diff --git a/README.md b/README.md new file mode 100644 index 00000000..f3399fcb --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +See the [wiki associated with this repository](https://github.com/Access4Learning/sif3-framework-java/wiki) for information on: + +* contributing to this framework +* the Java coding style to be used and +* the structure of the SIF 3 Framework repositories diff --git a/SIF3InfraREST/DB/.gitignore b/SIF3InfraREST/DB/.gitignore index 95e8b92f..e468a432 100644 --- a/SIF3InfraREST/DB/.gitignore +++ b/SIF3InfraREST/DB/.gitignore @@ -1,2 +1,3 @@ /SIF3InfrastructureERM.mwb /SIF3InfrastructureERM.mwb.bak +/JOBEvent.sql diff --git a/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_SQLite.sql b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_SQLite.sql new file mode 100644 index 00000000..1cad0029 --- /dev/null +++ b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_SQLite.sql @@ -0,0 +1,84 @@ +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB ( + JOB_ID INTEGER PRIMARY KEY AUTOINCREMENT, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED DATETIME NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED DATETIME NULL, + EXPIRE_DATETIME DATETIME NULL, + JOB_XML TEXT NULL); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_EVENT ( + JOB_EVENT_ID INTEGER PRIMARY KEY AUTOINCREMENT, + JOB_EVENT_DATETIME DATETIME NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR(1) NOT NULL, + FULL_UPDATE INTEGER NOT NULL DEFAULT 1, + TO_FINGERPRINT_ONLY INTEGER NOT NULL DEFAULT 1, + CONSUMER_REQUESTED INTEGER NOT NULL DEFAULT 1, + EVENT_PUBLISHED INTEGER NOT NULL DEFAULT 0, + PUBLISHED_DATETIME DATETIME NULL, + JOB_XML TEXT NULL); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + diff --git a/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mssql.sql b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mssql.sql new file mode 100644 index 00000000..dd0a3914 --- /dev/null +++ b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mssql.sql @@ -0,0 +1,86 @@ +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL IDENTITY, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED DATETIME NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED DATETIME NULL, + EXPIRE_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL IDENTITY, + JOB_EVENT_DATETIME DATETIME NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE BIT DEFAULT (1) NOT NULL, + TO_FINGERPRINT_ONLY BIT DEFAULT (1) NOT NULL, + CONSUMER_REQUESTED BIT DEFAULT (1) NOT NULL, + EVENT_PUBLISHED BIT DEFAULT (0) NOT NULL, + PUBLISHED_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + diff --git a/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mysql.sql b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mysql.sql new file mode 100644 index 00000000..eeac67de --- /dev/null +++ b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_mysql.sql @@ -0,0 +1,89 @@ +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)) +ENGINE = InnoDB; + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB ( + JOB_ID INT NOT NULL AUTO_INCREMENT, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED DATETIME NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED DATETIME NULL, + EXPIRE_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_ID)) +ENGINE = InnoDB; + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL AUTO_INCREMENT, + JOB_EVENT_DATETIME DATETIME NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR(1) NOT NULL, + FULL_UPDATE TINYINT(1) NOT NULL DEFAULT 1, + TO_FINGERPRINT_ONLY TINYINT(1) NOT NULL DEFAULT 1, + CONSUMER_REQUESTED TINYINT(1) NOT NULL DEFAULT 1, + EVENT_PUBLISHED TINYINT(1) NOT NULL DEFAULT 0, + PUBLISHED_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_EVENT_ID)) +ENGINE = InnoDB; + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); diff --git a/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_oracle.sql b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_oracle.sql new file mode 100644 index 00000000..e773d53e --- /dev/null +++ b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_oracle.sql @@ -0,0 +1,86 @@ +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR2(100) NOT NULL, + ADAPTER_TYPE VARCHAR2(20) NULL, + TEMPLATE_FILE_NAME VARCHAR2(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL, + JOB_REFID VARCHAR2(36) NOT NULL, + SERVICE_NAME VARCHAR2(256) NULL, + CURRENT_JOB_STATE VARCHAR2(30) NULL, + ENVIRONMENT_REFID VARCHAR2(36) NULL, + ADAPTER_TYPE VARCHAR2(20) NOT NULL, + FINGERPRINT VARCHAR2(256) NULL, + ZONE_ID VARCHAR2(256) NULL, + CONTEXT_ID VARCHAR2(255) NULL, + CREATED TIMESTAMP NOT NULL, + TIMEOUT_PERIOD VARCHAR2(30) NULL, + LAST_MODIFIED TIMESTAMP NULL, + EXPIRE_DATETIME TIMESTAMP NULL, + JOB_XML CLOB NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL, + JOB_EVENT_DATETIME TIMESTAMP NOT NULL, + JOB_REFID VARCHAR2(36) NOT NULL, + SERVICE_NAME VARCHAR2(256) NULL, + ENVIRONMENT_REFID VARCHAR2(36) NULL, + ADAPTER_TYPE VARCHAR2(20) NOT NULL, + FINGERPRINT VARCHAR2(256) NULL, + ZONE_ID VARCHAR2(256) NULL, + CONTEXT_ID VARCHAR2(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE CHAR DEFAULT 'Y' NOT NULL, + TO_FINGERPRINT_ONLY CHAR DEFAULT 'Y' NOT NULL, + CONSUMER_REQUESTED CHAR DEFAULT 'Y' NOT NULL, + EVENT_PUBLISHED CHAR DEFAULT 'N' NOT NULL, + PUBLISHED_DATETIME TIMESTAMP NULL, + JOB_XML CLOB NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + diff --git a/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_postgres.sql b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_postgres.sql new file mode 100644 index 00000000..f1ea4f85 --- /dev/null +++ b/SIF3InfraREST/DB/DDL/Datafix/current/v0.12.0-v0.13.0/Datafix_Job_postgres.sql @@ -0,0 +1,86 @@ +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED TIMESTAMP NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED TIMESTAMP NULL, + EXPIRE_DATETIME TIMESTAMP NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL, + JOB_EVENT_DATETIME TIMESTAMP NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE BOOLEAN DEFAULT 1 NOT NULL, + TO_FINGERPRINT_ONLY BOOLEAN DEFAULT 1 NOT NULL, + CONSUMER_REQUESTED BOOLEAN DEFAULT 1 NOT NULL, + EVENT_PUBLISHED BOOLEAN DEFAULT 0 NOT NULL, + PUBLISHED_DATETIME TIMESTAMP NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + diff --git a/SIF3InfraREST/DB/DDL/Initial_Inserts.sql b/SIF3InfraREST/DB/DDL/Initial_Inserts.sql index 0aa79f23..3efe9d36 100644 --- a/SIF3InfraREST/DB/DDL/Initial_Inserts.sql +++ b/SIF3InfraREST/DB/DDL/Initial_Inserts.sql @@ -5,3 +5,19 @@ insert into SIF3_ENV_TEMPLATE values ('DEV_LOCAL','devLocal.xml'); insert into SIF3_APP_TEMPLATE (APP_TEMPLATE_ID,SOLUTION_ID,APPLICATION_KEY,PASSWORD,USER_TOKEN,INSTANCE_ID,AUTH_METHOD,ENV_TEMPLATE_ID) values (1, 'test', 'TestSIS', 'Password1', null, null, 'Basic', 'DEV_LOCAL'); + + +-- ----------------------------------------------------- +-- Insert for Default Job Template +-- ----------------------------------------------------- +-- Consumer +INSERT INTO SIF3_JOB_TEMPLATE (JOB_TEMPLATE_ID,JOB_URL_NAME,ADAPTER_TYPE,TEMPLATE_FILE_NAME) +VALUES (1,'RolloverStudents','CONSUMER','rolloverStudentJob.xml'); + +-- Provider: DIRECT +INSERT INTO SIF3_JOB_TEMPLATE (JOB_TEMPLATE_ID,JOB_URL_NAME,ADAPTER_TYPE,TEMPLATE_FILE_NAME) +VALUES (2,'RolloverStudents','ENVIRONMENT_PROVIDER','rolloverStudentJob.xml'); + +-- Provider: BROKERED +INSERT INTO SIF3_JOB_TEMPLATE (JOB_TEMPLATE_ID,JOB_URL_NAME,ADAPTER_TYPE,TEMPLATE_FILE_NAME) +VALUES (3,'RolloverStudents','PROVIDER','rolloverStudentJob.xml'); diff --git a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_SQLite.sql b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_SQLite.sql index d179648c..d7f218d9 100644 --- a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_SQLite.sql +++ b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_SQLite.sql @@ -156,3 +156,88 @@ CREATE TABLE IF NOT EXISTS SIF3_SEC_SERVICE_PARAM ( CREATE INDEX IDX_EXT_SEC_SVC ON SIF3_SEC_SERVICE_PARAM (EXT_SECURITY_SERVICE_ID ASC); +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB ( + JOB_ID INTEGER PRIMARY KEY AUTOINCREMENT, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED DATETIME NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED DATETIME NULL, + EXPIRE_DATETIME DATETIME NULL, + JOB_XML TEXT NULL); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS SIF3_JOB_EVENT ( + JOB_EVENT_ID INTEGER PRIMARY KEY AUTOINCREMENT, + JOB_EVENT_DATETIME DATETIME NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR(1) NOT NULL, + FULL_UPDATE INTEGER NOT NULL DEFAULT 1, + TO_FINGERPRINT_ONLY INTEGER NOT NULL DEFAULT 1, + CONSUMER_REQUESTED INTEGER NOT NULL DEFAULT 1, + EVENT_PUBLISHED INTEGER NOT NULL DEFAULT 0, + PUBLISHED_DATETIME DATETIME NULL, + JOB_XML TEXT NULL); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + + diff --git a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mssql.sql b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mssql.sql index e5b3b767..ae512c6d 100644 --- a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mssql.sql +++ b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mssql.sql @@ -92,7 +92,7 @@ CREATE INDEX IDK_SUBSCR_ZONE_CTX_SVC ON SIF3_SUBSCRIPTION (ZONE_ID ASC, CONTEXT_ -- Table SIF3_ENV_TEMPLATE -- ----------------------------------------------------- -CREATE TABLE SIF3_ENV_TEMPLATE +CREATE TABLE SIF3_ENV_TEMPLATE ( ENV_TEMPLATE_ID VARCHAR(50) NOT NULL , TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL , @@ -102,7 +102,7 @@ CREATE TABLE SIF3_ENV_TEMPLATE -- ----------------------------------------------------- -- Table SIF3_APP_TEMPLATE -- ----------------------------------------------------- -CREATE TABLE SIF3_APP_TEMPLATE +CREATE TABLE SIF3_APP_TEMPLATE ( APP_TEMPLATE_ID INT NOT NULL , SOLUTION_ID VARCHAR(100) NULL , @@ -129,7 +129,7 @@ CREATE INDEX IDX_APP_TMPLT_TO_ENV_TMPLT ON SIF3_APP_TEMPLATE (ENV_TEMPLATE_ID AS -- ----------------------------------------------------- -- Table SIF3_EXT_SECURITY_SERVICE -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS SIF3_EXT_SECURITY_SERVICE ( +CREATE TABLE SIF3_EXT_SECURITY_SERVICE ( EXT_SECURITY_SERVICE_ID INT NOT NULL, AUTH_METHOD VARCHAR(20) NULL, ADAPTER_TYPE VARCHAR(20) NULL, @@ -145,7 +145,7 @@ CREATE UNIQUE INDEX UQ_AUTH_METH_ADAP ON SIF3_EXT_SECURITY_SERVICE (AUTH_METHOD -- ----------------------------------------------------- -- Table SIF3_SEC_SERVICE_PARAM -- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS SIF3_SEC_SERVICE_PARAM ( +CREATE TABLE SIF3_SEC_SERVICE_PARAM ( SEC_SERVICE_PARAM_ID INT NOT NULL, EXT_SECURITY_SERVICE_ID INT NOT NULL, PARAM_NAME VARCHAR(45) NULL, @@ -159,3 +159,90 @@ CREATE TABLE IF NOT EXISTS SIF3_SEC_SERVICE_PARAM ( ); CREATE INDEX IDX_EXT_SEC_SVC ON SIF3_SEC_SERVICE_PARAM (EXT_SECURITY_SERVICE_ID ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL IDENTITY, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED DATETIME NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED DATETIME NULL, + EXPIRE_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL IDENTITY, + JOB_EVENT_DATETIME DATETIME NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE BIT DEFAULT (1) NOT NULL, + TO_FINGERPRINT_ONLY BIT DEFAULT (1) NOT NULL, + CONSUMER_REQUESTED BIT DEFAULT (1) NOT NULL, + EVENT_PUBLISHED BIT DEFAULT (0) NOT NULL, + PUBLISHED_DATETIME DATETIME NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + diff --git a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mysql.sql b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mysql.sql index 20cd841a..fea25d44 100644 --- a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mysql.sql +++ b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_mysql.sql @@ -193,6 +193,100 @@ ENGINE = InnoDB; CREATE INDEX `IDX_EXT_SEC_SVC` ON `SIF3_SEC_SERVICE_PARAM` (`EXT_SECURITY_SERVICE_ID` ASC); +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `SIF3_JOB_TEMPLATE` ( + `JOB_TEMPLATE_ID` INT NOT NULL, + `JOB_URL_NAME` VARCHAR(100) NOT NULL, + `ADAPTER_TYPE` VARCHAR(20) NULL, + `TEMPLATE_FILE_NAME` VARCHAR(100) NOT NULL, + PRIMARY KEY (`JOB_TEMPLATE_ID`)) +ENGINE = InnoDB; + +CREATE UNIQUE INDEX `UQ_JOB_URL_NAME` ON `SIF3_JOB_TEMPLATE` (`JOB_URL_NAME` ASC, `ADAPTER_TYPE` ASC); + + +-- ----------------------------------------------------- +-- Table `SIF3_JOB` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `SIF3_JOB` ; + +CREATE TABLE IF NOT EXISTS `SIF3_JOB` ( + `JOB_ID` INT NOT NULL AUTO_INCREMENT, + `JOB_REFID` VARCHAR(36) NOT NULL, + `SERVICE_NAME` VARCHAR(256) NULL, + `CURRENT_JOB_STATE` VARCHAR(30) NULL, + `ENVIRONMENT_REFID` VARCHAR(36) NULL, + `ADAPTER_TYPE` VARCHAR(20) NOT NULL, + `FINGERPRINT` VARCHAR(256) NULL, + `ZONE_ID` VARCHAR(256) NULL, + `CONTEXT_ID` VARCHAR(255) NULL, + `CREATED` DATETIME NOT NULL, + `TIMEOUT_PERIOD` VARCHAR(30) NULL, + `LAST_MODIFIED` DATETIME NULL, + `EXPIRE_DATETIME` DATETIME NULL, + `JOB_XML` TEXT NULL, + PRIMARY KEY (`JOB_ID`)) +ENGINE = InnoDB; + +CREATE INDEX `JOB_JOBREFID_ADPTYPE_IDX` ON `SIF3_JOB` (`JOB_REFID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_FINGERPRT_ADPTYPE_IDX` ON `SIF3_JOB` (`FINGERPRINT` ASC, `SERVICE_NAME` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_ENVREFID_ADPTYPE_IDX` ON `SIF3_JOB` (`FINGERPRINT` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_ZONEID_ADPTYPE_IDX` ON `SIF3_JOB` (`ZONE_ID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_REFID_ENVREFID_ADPTYPE_IDX` ON `SIF3_JOB` (`JOB_REFID` ASC, `ENVIRONMENT_REFID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_SVCNNAME_ADPTYPE_IDX` ON `SIF3_JOB` (`SERVICE_NAME` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOB_EXPIRE_IDX` ON `SIF3_JOB` (`EXPIRE_DATETIME` ASC); + +-- ----------------------------------------------------- +-- Table `SIF3_JOB_EVENT` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `SIF3_JOB_EVENT` ; + +CREATE TABLE IF NOT EXISTS `SIF3_JOB_EVENT` ( + `JOB_EVENT_ID` INT NOT NULL AUTO_INCREMENT, + `JOB_EVENT_DATETIME` DATETIME NOT NULL, + `JOB_REFID` VARCHAR(36) NOT NULL, + `SERVICE_NAME` VARCHAR(256) NULL, + `ENVIRONMENT_REFID` VARCHAR(36) NULL, + `ADAPTER_TYPE` VARCHAR(20) NOT NULL, + `FINGERPRINT` VARCHAR(256) NULL, + `ZONE_ID` VARCHAR(256) NULL, + `CONTEXT_ID` VARCHAR(256) NULL, + `EVENT_TYPE` CHAR(1) NOT NULL, + `FULL_UPDATE` TINYINT(1) NOT NULL DEFAULT 1, + `TO_FINGERPRINT_ONLY` TINYINT(1) NOT NULL DEFAULT 1, + `CONSUMER_REQUESTED` TINYINT(1) NOT NULL DEFAULT 1, + `EVENT_PUBLISHED` TINYINT(1) NOT NULL DEFAULT 0, + `PUBLISHED_DATETIME` DATETIME NULL, + `JOB_XML` TEXT NULL, + PRIMARY KEY (`JOB_EVENT_ID`)) +ENGINE = InnoDB; + +CREATE INDEX `JOBEVT_DT_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`JOB_EVENT_DATETIME` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_JOBREFID_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`JOB_REFID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_FINGERPRT_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`FINGERPRINT` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_ENVREFID_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`ENVIRONMENT_REFID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_ZONEID_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`ZONE_ID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_DT_EVTY_ADPTY_PUBL_IDX` ON `SIF3_JOB_EVENT` (`JOB_EVENT_DATETIME` ASC, `ADAPTER_TYPE` ASC, `EVENT_TYPE` ASC, `EVENT_PUBLISHED` ASC); + +CREATE INDEX `JOBEVT_DT__ADPTY_PUBL_IDX` ON `SIF3_JOB_EVENT` (`JOB_EVENT_DATETIME` ASC, `ADAPTER_TYPE` ASC, `EVENT_PUBLISHED` ASC); + +CREATE INDEX `JOBEVT_JOBID_ENVID_ADPTY_IDX` ON `SIF3_JOB_EVENT` (`JOB_REFID` ASC, `ENVIRONMENT_REFID` ASC, `ADAPTER_TYPE` ASC); + +CREATE INDEX `JOBEVT_SVCNAME_ADPTYPE_IDX` ON `SIF3_JOB_EVENT` (`SERVICE_NAME` ASC, `ADAPTER_TYPE` ASC); + SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; diff --git a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_oracle.sql b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_oracle.sql index 8721b643..1b896bcb 100644 --- a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_oracle.sql +++ b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_oracle.sql @@ -159,6 +159,91 @@ REFERENCES SIF3_EXT_SECURITY_SERVICE (EXT_SECURITY_SERVICE_ID); CREATE INDEX IDX_EXT_SEC_SVC ON SIF3_SEC_SERVICE_PARAM (EXT_SECURITY_SERVICE_ID ASC); +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR2(100) NOT NULL, + ADAPTER_TYPE VARCHAR2(20) NULL, + TEMPLATE_FILE_NAME VARCHAR2(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL, + JOB_REFID VARCHAR2(36) NOT NULL, + SERVICE_NAME VARCHAR2(256) NULL, + CURRENT_JOB_STATE VARCHAR2(30) NULL, + ENVIRONMENT_REFID VARCHAR2(36) NULL, + ADAPTER_TYPE VARCHAR2(20) NOT NULL, + FINGERPRINT VARCHAR2(256) NULL, + ZONE_ID VARCHAR2(256) NULL, + CONTEXT_ID VARCHAR2(255) NULL, + CREATED TIMESTAMP NOT NULL, + TIMEOUT_PERIOD VARCHAR2(30) NULL, + LAST_MODIFIED TIMESTAMP NULL, + EXPIRE_DATETIME TIMESTAMP NULL, + JOB_XML CLOB NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL, + JOB_EVENT_DATETIME TIMESTAMP NOT NULL, + JOB_REFID VARCHAR2(36) NOT NULL, + SERVICE_NAME VARCHAR2(256) NULL, + ENVIRONMENT_REFID VARCHAR2(36) NULL, + ADAPTER_TYPE VARCHAR2(20) NOT NULL, + FINGERPRINT VARCHAR2(256) NULL, + ZONE_ID VARCHAR2(256) NULL, + CONTEXT_ID VARCHAR2(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE CHAR DEFAULT 'Y' NOT NULL, + TO_FINGERPRINT_ONLY CHAR DEFAULT 'Y' NOT NULL, + CONSUMER_REQUESTED CHAR DEFAULT 'Y' NOT NULL, + EVENT_PUBLISHED CHAR DEFAULT 'N' NOT NULL, + PUBLISHED_DATETIME TIMESTAMP NULL, + JOB_XML CLOB NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + -- ----------------------------------------------------- -- Sequence for AUTO_INCREMENT -- ----------------------------------------------------- diff --git a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_postgres.sql b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_postgres.sql index 9e068663..77236a99 100644 --- a/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_postgres.sql +++ b/SIF3InfraREST/DB/DDL/SIF3InfrastructureERM_DDL_postgres.sql @@ -159,6 +159,92 @@ REFERENCES SIF3_EXT_SECURITY_SERVICE (EXT_SECURITY_SERVICE_ID); CREATE INDEX IDX_EXT_SEC_SVC ON SIF3_SEC_SERVICE_PARAM (EXT_SECURITY_SERVICE_ID ASC); +-- ----------------------------------------------------- +-- Table SIF3_JOB_TEMPLATE +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_TEMPLATE ( + JOB_TEMPLATE_ID INT NOT NULL, + JOB_URL_NAME VARCHAR(100) NOT NULL, + ADAPTER_TYPE VARCHAR(20) NULL, + TEMPLATE_FILE_NAME VARCHAR(100) NOT NULL, + PRIMARY KEY (JOB_TEMPLATE_ID)); + +CREATE UNIQUE INDEX UQ_JOB_URL_NAME ON SIF3_JOB_TEMPLATE (JOB_URL_NAME ASC, ADAPTER_TYPE ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB ( + JOB_ID INT NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + CURRENT_JOB_STATE VARCHAR(30) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(255) NULL, + CREATED TIMESTAMP NOT NULL, + TIMEOUT_PERIOD VARCHAR(30) NULL, + LAST_MODIFIED TIMESTAMP NULL, + EXPIRE_DATETIME TIMESTAMP NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_ID)); + +CREATE INDEX JOB_JOBREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_FINGERPRT_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_ZONEID_ADPTYPE_IDX ON SIF3_JOB (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_REFID_ENVREFID_ADPTYPE_IDX ON SIF3_JOB (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_SVCNNAME_ADPTYPE_IDX ON SIF3_JOB (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOB_EXPIRE_IDX ON SIF3_JOB (EXPIRE_DATETIME ASC); + +-- ----------------------------------------------------- +-- Table SIF3_JOB_EVENT +-- ----------------------------------------------------- +CREATE TABLE SIF3_JOB_EVENT ( + JOB_EVENT_ID INT NOT NULL, + JOB_EVENT_DATETIME TIMESTAMP NOT NULL, + JOB_REFID VARCHAR(36) NOT NULL, + SERVICE_NAME VARCHAR(256) NULL, + ENVIRONMENT_REFID VARCHAR(36) NULL, + ADAPTER_TYPE VARCHAR(20) NOT NULL, + FINGERPRINT VARCHAR(256) NULL, + ZONE_ID VARCHAR(256) NULL, + CONTEXT_ID VARCHAR(256) NULL, + EVENT_TYPE CHAR NOT NULL, + FULL_UPDATE BOOLEAN DEFAULT 1 NOT NULL, + TO_FINGERPRINT_ONLY BOOLEAN DEFAULT 1 NOT NULL, + CONSUMER_REQUESTED BOOLEAN DEFAULT 1 NOT NULL, + EVENT_PUBLISHED BOOLEAN DEFAULT 0 NOT NULL, + PUBLISHED_DATETIME TIMESTAMP NULL, + JOB_XML TEXT NULL, + PRIMARY KEY (JOB_EVENT_ID)); + +CREATE INDEX JOBEVT_DT_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_JOBREFID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_FINGERPRT_ADPTY_IDX ON SIF3_JOB_EVENT (FINGERPRINT ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ENVREFID_ADPTY_IDX ON SIF3_JOB_EVENT (ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_ZONEID_ADPTY_IDX ON SIF3_JOB_EVENT (ZONE_ID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_DT_EVTY_ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_DT__ADPTY_PUBL_IDX ON SIF3_JOB_EVENT (JOB_EVENT_DATETIME ASC, ADAPTER_TYPE ASC, EVENT_PUBLISHED ASC); + +CREATE INDEX JOBEVT_JOBID_ENVID_ADPTY_IDX ON SIF3_JOB_EVENT (JOB_REFID ASC, ENVIRONMENT_REFID ASC, ADAPTER_TYPE ASC); + +CREATE INDEX JOBEVT_SVCNAME_ADPTYPE_IDX ON SIF3_JOB_EVENT (SERVICE_NAME ASC, ADAPTER_TYPE ASC); + -- ----------------------------------------------------- -- Sequence for AUTO_INCREMENT -- ----------------------------------------------------- diff --git a/SIF3InfraREST/DB/Data/SIF3Infra.sqliteDB b/SIF3InfraREST/DB/Data/SIF3Infra.sqliteDB index eebd0df0..46c3a492 100644 Binary files a/SIF3InfraREST/DB/Data/SIF3Infra.sqliteDB and b/SIF3InfraREST/DB/Data/SIF3Infra.sqliteDB differ diff --git a/SIF3InfraREST/README.md b/SIF3InfraREST/README.md index 7ef370ad..ded8229b 100644 --- a/SIF3InfraREST/README.md +++ b/SIF3InfraREST/README.md @@ -25,7 +25,7 @@ Project (note that the version number will change over time): sif3.framework sif3-infra-rest - 0.12.0 + 0.13.0 ``` @@ -165,6 +165,15 @@ project is upgraded properly to the new framework version.** **Please refer to the detailed release notes in "release/v0.12.0" carefully to ensure that your project is upgraded properly to the new framework version.** +## Version from Sept 25, 2018: v0.13.0 - Various changes +- Fixed a number of minor issues. +- Added SIFException to many Provider Interface methods to enable providers to throw a "generic" exceptions where the + provider can customise the HTTP Status Code and error message to be returned to the consumer. +- Added Functional Services functionality. Usage of this functionality is detailed in the developer's guide in + section 5.11. + +**Please refer to the detailed release notes in "release/v0.13.0" carefully to ensure that your project is upgraded properly to the new framework version.** + # Download Instructions How to download this project: diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/pom.xml b/SIF3InfraREST/SIF3Demo/sif3-demo-web/pom.xml index 6bc4350e..0e3b7c27 100644 --- a/SIF3InfraREST/SIF3Demo/sif3-demo-web/pom.xml +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/pom.xml @@ -8,7 +8,7 @@ sif3.framework sif3-demo-web war - 0.12.0-Demo + 0.13.0-Demo SIF3 Demo Provider @@ -41,7 +41,7 @@ sif3.framework sif3-infra-rest - 0.12.0-beta + 0.13.0-beta @@ -51,7 +51,7 @@ sifau sif3-au-datamodel - 3.4.1 + 3.4.3 diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/BaseFunctionalConsumer.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/BaseFunctionalConsumer.java new file mode 100644 index 00000000..6a7bcb98 --- /dev/null +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/BaseFunctionalConsumer.java @@ -0,0 +1,221 @@ +/* + * BaseFunctionalConsumer.java + * Created: 26 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package systemic.sif3.demo.rest.consumer.functional; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.util.Date; +import java.util.HashMap; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.DateUtils; +import sif3.common.model.EventMetadata; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFEvent; +import sif3.common.model.SIFZone; +import sif3.common.model.delayed.DelayedResponseReceipt; +import sif3.common.model.job.PhaseInfo; +import sif3.common.utils.JAXBUtils; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.ErrorDetails; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.job.PhaseDataResponse; +import sif3.common.ws.model.MultiOperationStatusList; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer; + +/** + * Note: All the processABC() methods implemented in this class are for demo purpose only. It simply logs the returned response to any + * delayed requests. In actual real world implementations these methods need to be implemented properly, meaning some action is performed + * on the data store that relates to the received response. + * + * @author Joerg Huber + */ +public abstract class BaseFunctionalConsumer extends AbstractFunctionalServiceConsumer +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final static String RECORD_MARKER = "\n================================================================================================\n"; + private HashMap recordCounters = new HashMap(); + private static Boolean writePayload = null; + + public BaseFunctionalConsumer() + { + super(); + + //Initialise JAXB context for Job classes. Make consumer behave better against race conditions. + JAXBUtils.initCtx(getMultiObjectClassInfo().getObjectType()); + JAXBUtils.initCtx(getSingleObjectClassInfo().getObjectType()); + } + + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedJobsCreate(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedJobsCreate(MultiOperationStatusList statusList, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED JOB CREATE Response for "+getServiceURLNamePlural()+":\n"+statusList+"\nDelayed Receipt Details:\n"+receipt); + + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedJobsDelete(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedJobsDelete(MultiOperationStatusList statusList, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED JOB DELETE Response for "+getServiceURLNamePlural()+":\n"+statusList+"\nDelayed Receipt Details:\n"+receipt); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedJobsQuery(sif3.infra.common.model.JobCollectionType, sif3.common.model.PagingInfo, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedJobsQuery(JobCollectionType jobs, PagingInfo pagingInfo, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED JOB QUERY Response for "+getServiceURLNamePlural()+":\n"+jobs+"\nPagingInfo:\n"+pagingInfo+"\nDelayed Receipt Details:\n"+receipt); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedPhaseQuery(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataResponse, sif3.common.model.PagingInfo, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedPhaseQuery(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, PagingInfo pagingInfo, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED PHASE QUERY Response:\nPhase Info: "+phaseInfo+"\nPagingInfo:\n"+pagingInfo+"\nDelayed Receipt Details:\n"+receipt+"\nPhase Response Data:\n"+phaseDataResponse); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedPhaseCreate(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataResponse, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedPhaseCreate(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED PHASE CREATE Response:\nPhase Info: "+phaseInfo+"\nDelayed Receipt Details:\n"+receipt+"\nPhase Response Data:\n"+phaseDataResponse); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedPhaseUpdate(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataResponse, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedPhaseUpdate(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED PHASE UPDATE Response:\nPhase Info: "+phaseInfo+"\nDelayed Receipt Details:\n"+receipt+"\nPhase Response Data:\n"+phaseDataResponse); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processDelayedPhaseDelete(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataResponse, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedPhaseDelete(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED PHASE DELETE Response:\nPhase Info: "+phaseInfo+"\nDelayed Receipt Details:\n"+receipt+"\nPhase Response Data:\n"+phaseDataResponse); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.BaseConsumer#processDelayedError(sif3.common.ws.ErrorDetails, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void processDelayedError(ErrorDetails error, DelayedResponseReceipt receipt) + { + logger.debug("Received DELAYED ERROR Response:\n"+error+"\nDelayed Receipt Details:\n"+receipt); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#processJobEvent(sif3.common.model.SIFEvent, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.EventMetadata, java.lang.String, java.lang.String) + */ + @Override + public void processJobEvent(SIFEvent sifEvent, SIFZone zone, SIFContext context, EventMetadata metadata, String msgReadID, String consumerID) + { + String consumerName = getPrettyName()+"(QueueID:"+msgReadID+"; ConsumerID: "+consumerID+")"; + logger.debug(consumerName +" received an event from Zone = "+zone+", Context = "+context+" and Event Metadata = "+metadata); + dumpJobEvent(sifEvent, zone, context, msgReadID, consumerID); + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + + private void dumpJobEvent(SIFEvent sifEvent, SIFZone zone, SIFContext context, String msgReadID, String consumerID) + { +// logger.debug(consumerID +" write data to file..."); + String filename = getFileNameOnly(consumerID); + String fullFileName= getFullFileName(consumerID); + Integer recordNum = recordCounters.get(filename); + if (recordNum == null) + { + recordNum = 1; + recordCounters.put(filename, recordNum); + } + String timestamp = DateUtils.getISO8601withSecFraction(new Date()); + + BufferedWriter out = null; + try + { + FileWriter fstream = new FileWriter(fullFileName, true); + out = new BufferedWriter(fstream); + out.write(RECORD_MARKER); + out.write("Record " + recordNum + " - processed by Thread ID = "+Thread.currentThread().getId()+"\n"+sifEvent.getListSize()+" "+sifEvent.getEventAction().name()+" Events from Queue Reader "+ msgReadID+"\nReceived at "+timestamp+" from Zone = "+zone.getId()+" and Context = "+context.getId()); + out.write(RECORD_MARKER); + + if (writePayload == null) + { + writePayload = getServiceProperties().getPropertyAsBool("test.consumer.write.payload", true); + } + + if (writePayload) + { + out.write(getMarshaller().marshal(sifEvent.getSIFObjectList(), MediaType.APPLICATION_XML_TYPE)); + } + recordNum++; + recordCounters.put(filename, recordNum); + } + catch (Exception ex) + { + logger.error("Failed to write data to dump file with name:" + fullFileName, ex); + } + finally + { + if (out != null) + { + try + { + out.close(); + } + catch (Exception ex) {} // nothing we can do + } + } + } + + private String getFullFileName(String consumerID) + { + return getServiceProperties().getPropertyAsString("test.tempDir.output","") + "/" + getFileNameOnly(consumerID); + + } + + private String getFileNameOnly(String consumerID) + { + return consumerID+".log"; + } +} diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/RolloverStudentConsumer.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/RolloverStudentConsumer.java new file mode 100644 index 00000000..97564883 --- /dev/null +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/consumer/functional/RolloverStudentConsumer.java @@ -0,0 +1,58 @@ +/* + * RolloverStudentConsumer.java + * Created: 26 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package systemic.sif3.demo.rest.consumer.functional; + +/** + * @author Joerg Huber + * + */ +public class RolloverStudentConsumer extends BaseFunctionalConsumer +{ + + public RolloverStudentConsumer() + { + super(); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#getServiceURLNamePlural() + */ + @Override + public String getServiceURLNamePlural() + { + return "RolloverStudents"; + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer#getServiceURLNameSingular() + */ + @Override + public String getServiceURLNameSingular() + { + return "RolloverStudent"; + } + + /* (non-Javadoc) + * @see sif3.infra.rest.consumer.BaseConsumer#shutdown() + */ + @Override + public void shutdown() + { + logger.info("Shutting down Functional Service Consumer '"+getServiceURLNamePlural()+"'"); + } +} diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/StudentPersonalProvider.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/StudentPersonalProvider.java index 5343a9b6..f541a0be 100644 --- a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/StudentPersonalProvider.java +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/StudentPersonalProvider.java @@ -39,6 +39,7 @@ import sif3.common.conversion.ModelObjectInfo; import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnmarshalException; import sif3.common.exception.UnsupportedMediaTypeExcpetion; import sif3.common.exception.UnsupportedQueryException; @@ -339,7 +340,7 @@ public Object retrieve(SIFZone zone, SIFContext context, PagingInfo pagingInfo, if (pagingInfo == null) { - throw new DataTooLargeException("No paging info is provided. Please provide navigationPage and navigationPageSize."); + throw new DataTooLargeException("No paging info is provided.", "Please provide navigationPage and navigationPageSize.", "Provider ("+getProviderName()+")"); } else { @@ -372,7 +373,7 @@ public Object retrieve(SIFZone zone, SIFContext context, PagingInfo pagingInfo, */ @Override public Object retrieveByServicePath(QueryCriteria queryCriteria, SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata, ResponseParameters customResponseParams) - throws PersistenceException, UnsupportedQueryException, DataTooLargeException + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException { logger.debug("Performing query by service path."); if (logger.isDebugEnabled()) @@ -407,12 +408,12 @@ else if ("TeachingGroups".equals(predicates.get(0).getSubject())) } else { - throw new UnsupportedQueryException("The query condition (driven by the service path) "+queryCriteria+" is not supported by the provider."); + throw new UnsupportedQueryException("Unsupported ServicePath.", "The query condition (driven by the service path) "+queryCriteria+" is not supported by the provider.", "Provider ("+getProviderName()+")"); } } else // not supported query (only single level service path query supported by this provider) { - throw new UnsupportedQueryException("The query condition (driven by the service path) "+queryCriteria+" is not supported by the provider."); + throw new UnsupportedQueryException("Unsupported ServicePath.", "The query condition (driven by the service path) "+queryCriteria+" is not supported by the provider.", "Provider ("+getProviderName()+")"); } } @@ -421,7 +422,7 @@ else if ("TeachingGroups".equals(predicates.get(0).getSubject())) * @see sif3.common.interfaces.QueryProvider#retrieveByQBE(java.lang.Object, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) */ public Object retrieveByQBE(Object exampleObject, SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata, ResponseParameters customResponseParams) - throws PersistenceException, UnsupportedQueryException, DataTooLargeException + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException { logger.debug("Performing QBE query for: "+exampleObject); if (exampleObject instanceof StudentPersonalType) diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/functional/RolloverStudentsProvider.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/functional/RolloverStudentsProvider.java new file mode 100644 index 00000000..ee58f38b --- /dev/null +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/main/java/systemic/sif3/demo/rest/provider/functional/RolloverStudentsProvider.java @@ -0,0 +1,439 @@ +/* + * RolloverStudentsProvider.java + * Created: 7 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package systemic.sif3.demo.rest.provider.functional; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.conversion.ModelObjectInfo; +import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.exception.UnsupportedQueryException; +import sif3.common.header.HeaderProperties; +import sif3.common.model.PagingInfo; +import sif3.common.model.RequestMetadata; +import sif3.common.model.ResponseParameters; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.job.PhaseInfo; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.common.ws.job.PhaseDataResponse; +import sif3.infra.common.model.JobType; +import sif3.infra.rest.provider.BaseFunctionalServiceProvider; + +/** + * @author Joerg Huber + * + */ +public class RolloverStudentsProvider extends BaseFunctionalServiceProvider +{ + private static int numOperations = 0; + + /*--------------------*/ + /*-- Job Operations --*/ + /*--------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.rest.provider.BaseFunctionalServiceProvider#createNewJob(sif3.infra.common.model.JobType, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public JobType createNewJob(JobType jobData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException + { + logger.debug("Create Job for Functional Service "+getMultiObjectClassInfo().getObjectName()+"..."); + + // At the moment we do nothing... A real implementation may need to do some stuff here. + if (jobData.getInitialization() != null) + { + logger.debug("Initialisation values:\nInitialisation Phase: "+jobData.getInitialization().getPhaseName()+"\nInitialisation Parameters: "+jobData.getInitialization().getPayload()); + } + else + { + logger.debug("No initialisation provided for Job."); + } + + // return the final job object. Generally that is the same as the one given to this method. + return jobData; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#deleteJob(java.lang.String, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public boolean deleteJob(String jobID, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException + { + logger.debug("Delete Job for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = '"+jobID+"'"); + + // Generally the implementation would check here what needs to be done to remove a job. If the implementation no longer + // knows the job (i.e. has been removed in the past) then it must return false. If the job is still known it must be removed and true + // must be returned. + + // We pretend to fail every 3rd delete just for the sake of testing real world scenarios where failure occur. + return ((numOperations++ % 3) != 0); + } + + /*----------------------*/ + /*-- Phase Operations --*/ + /*----------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#retrieveDataFromPhase(java.lang.String, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters, javax.ws.rs.core.MediaType) + */ + @Override + public PhaseDataResponse retrieveDataFromPhase(PhaseInfo phaseInfo, + SIFZone zone, + SIFContext context, + PagingInfo pagingInfo, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, DataTooLargeException, SIFException + { + logger.debug("retrieveDataFromPhase for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = "+phaseInfo.getJobID()+" and phase "+phaseInfo.getPhaseName()+" called."); + logger.debug("Requested Return Mime Type: "+returnMimeType.toString()+"\nPaging Info: "+pagingInfo); + + // Generally the implementation would check here what needs to be returned for the given phase. + // General flow would be: + // 1) Check the phase for the given job and based on this... + // 2) Query the provider's data store for data. + // 3) Marshal the result into String with the format indicated by the 'returnMimeType'. + // Suggestion: Use the MarshallFactory class of this framework. + // 4) If all is ok then return the resulting String. If anything fails raise appropriate exception. + // Note: Null can be returned which indicates no or no further data available for this phase. This will be + // translated into an appropriate value being returned to the consumer (SIF Specification states that + // this will return a HTTP Status 204 (NO_CONTENT).) + // + // As part of this call the provider may also update the job and/or phase state using appropriate updateXYZ() methods + // of the BaseFunctionalServiceProvider class. + + // For testing an illustration purpose we create the odd error... + if ((pagingInfo == null) || (pagingInfo.getPageSize() > 100)) + { + throw new DataTooLargeException("Pagin Info invalid.","Paging info not provided or page size is larger than 100.", "Provider ("+getProviderName()+")"); + } + + if (phaseInfo.getPhaseName().equalsIgnoreCase("oldYearEnrolment")) + { + if (!returnMimeType.isCompatible(MediaType.APPLICATION_XML_TYPE)) + { + throw new UnsupportedMediaTypeExcpetion(returnMimeType.toString()+" is not accepted. Valid mime type is "+ MediaType.APPLICATION_XML_TYPE.toString()); + } + + //make up some dummy data.. + PhaseDataResponse response = new PhaseDataResponse(); + response.setMimeType(returnMimeType); + response.setData( + "\n"+ + " \n"+ + " 2017\n"+ + " 4001\n"+ + " \n"+ + " \n"+ + " 2018\n"+ + " 4002\n"+ + " \n"+ + ""); + + return response; + } + + // For all other phase we return nothing.... + return null; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#createDataInPhase(java.lang.String, java.lang.String, java.lang.String, javax.ws.rs.core.MediaType, boolean, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public PhaseDataResponse createDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException + { + logger.debug("createDataInPhase for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = "+phaseInfo.getJobID()+" and phase "+phaseInfo.getPhaseName()+" called."); + logger.debug("\nData:\n"+phaseData.getData()+"\nData Mime Type: "+phaseData.getMimeType().toString()+"\nUse Advisory: "+useAdvisory); + + // Generally the implementation would check here what needs to be done to create data for the given phase. + // General flow would be: + // 1) Check the phase for the given job and based on this... + // 2) Unmarshal the 'phaseData.data'. The 'phaseData.mimeType' indicates the mime type of the 'data'. + // Suggestion: Use the UnmarshallFactory class of this framework. Note it is entirely valid that there is + // no data given. It is up to the binding documentation and the agreed contract between consumer + // and provider if data is passed to this method. + // 3) If all is ok then process the data as requested. If anything fails raise appropriate exception. + // 4) If data shall be returned then create appropriate PhaseDataResponse with the data marshalled to a String using + // the returnMimeType. + // + // It is suggested that the implementation of this method may use asynchronous processing, meaning that + // The request might be put on an internal queue or stored in DB for later processing. This is important + // as it is expected that this method finishes execution in a timely manner so that a connection to the + // caller doesn't remain open for a long time. + // + // As part of this call the provider may also update the job and/or phase state using appropriate updateXYZ() methods + // of the BaseFunctionalServiceProvider class. + + // For testing purposes we fail every 5th operation... + if ((numOperations++ % 5) == 2) + { + throw new SIFException(Status.BAD_REQUEST.getStatusCode(), "Failed to create data.", "Data could not be created in phase "+phaseInfo.getPhaseName()+" for job with ID = "+phaseInfo.getJobID(), getMultiObjectClassInfo().getObjectName()+" Provider."); + } + + if (phaseInfo.getPhaseName().equalsIgnoreCase("oldYearEnrolment")) + { + if (!returnMimeType.isCompatible(MediaType.APPLICATION_XML_TYPE)) + { + throw new UnsupportedMediaTypeExcpetion(returnMimeType.toString()+" is not accepted. Valid mime type is "+ MediaType.APPLICATION_XML_TYPE.toString()); + } + + //make up some dummy data.. + PhaseDataResponse response = new PhaseDataResponse(); + response.setMimeType(returnMimeType); + response.setStatus(Status.OK); // This is an override of the default of 201! + response.setData( + "\n"+ + " \n"+ + " 2017\n"+ + " 4001\n"+ + " 201\n"+ + " \n"+ + " \n"+ + " 2018\n"+ + " 4002\n"+ + " 406\n"+ + " Invalid data for this year and school.\n"+ + " \n"+ + ""); + + return response; + } + + // For all other phases we return null. This should default the response status to 201. + return null; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#updateDataInPhase(java.lang.String, java.lang.String, java.lang.String, javax.ws.rs.core.MediaType, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public PhaseDataResponse updateDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException + { + logger.debug("updateDataInPhase for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = "+phaseInfo.getJobID()+" and phase "+phaseInfo.getPhaseName()+" called."); + logger.debug("\nData:\n"+phaseData.getData()+"\nData Mime Type: "+phaseData.getMimeType().toString()); + + // Generally the implementation would check here what needs to be done to update data for the given phase. + // General flow would be: + // 1) Check the phase for the given job and based on this... + // 2) Unmarshal the 'phaseData.data'. The 'phaseData.mimeType' indicates the mime type of the 'data'. + // Suggestion: Use the UnmarshallFactory class of this framework. Note it is entirely valid that there is + // no data given. It is up to the binding documentation and the agreed contract between consumer + // and provider if data is passed to this method. + // 3) If all is ok then process the data as requested. If anything fails raise appropriate exception. + // 4) If data shall be returned then create appropriate PhaseDataResponse with the data marshalled to a String using + // the returnMimeType. + // + // It is suggested that the implementation of this method may use asynchronous processing, meaning that + // The request might be put on an internal queue or stored in DB for later processing. This is important + // as it is expected that this method finishes execution in a timely manner so that a connection to the + // caller doesn't remain open for a long time. + // + // As part of this call the provider may also update the job and/or phase state using appropriate updateXYZ() methods + // of the BaseFunctionalServiceProvider class. + + // For testing purposes we fail every 4th operation... + if ((numOperations++ % 4) == 0) + { + throw new SIFException(Status.BAD_REQUEST.getStatusCode(), "Failed to update data.", "Data could not be updated in phase "+phaseInfo.getPhaseName()+" for job with ID = "+phaseInfo.getJobID(), getMultiObjectClassInfo().getObjectName()+" Provider."); + } + + // For the time being we return null. This should default the response status to 200. + return null; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#deleteDataInPhase(java.lang.String, java.lang.String, java.lang.String, javax.ws.rs.core.MediaType, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public PhaseDataResponse deleteDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException + { + logger.debug("deleteDataInPhase for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = "+phaseInfo.getJobID()+" and phase "+phaseInfo.getPhaseName()+" called."); + logger.debug("\nData:\n"+phaseData.getData()+"\nData Mime Type: "+phaseData.getMimeType().toString()); + + + // Generally the implementation would check here what needs to be done to delete data for the given phase. + // General flow would be: + // 1) Check the phase for the given job and based on this... + // 2) Unmarshal the 'phaseData.data'. The 'phaseData.mimeType' indicates the mime type of the 'data'. + // Suggestion: Use the UnmarshallFactory class of this framework. Note it is entirely valid that there is + // no data given. It is up to the binding documentation and the agreed contract between consumer + // and provider if data is passed to this method. + // 3) If all is ok then process the data as requested. If anything fails raise appropriate exception. + // 4) If data shall be returned then create appropriate PhaseDataResponse with the data marshalled to a String using + // the returnMimeType. + // + // It is suggested that the implementation of this method may use asynchronous processing, meaning that + // The request might be put on an internal queue or stored in DB for later processing. This is important + // as it is expected that this method finishes execution in a timely manner so that a connection to the + // caller doesn't remain open for a long time. + // + // As part of this call the provider may also update the job and/or phase state using appropriate updateXYZ() methods + // of the BaseFunctionalServiceProvider class. + + if ((numOperations++ % 4) == 0) + { + throw new SIFException(Status.BAD_REQUEST.getStatusCode(), "Failed to delete data.", "Data could not be deleted in phase "+phaseInfo.getPhaseName()+" for job with ID = "+phaseInfo.getJobID(), getMultiObjectClassInfo().getObjectName()+" Provider."); + } + + // For the time being we return null. This should default the response status to 204. + return null; + } + + /*----------------------------*/ + /*-- Phase State Operations --*/ + /*----------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#phaseStateUpdatedByConsumer(java.lang.String, java.lang.String, sif3.common.CommonConstants.PhaseState, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public void phaseStateUpdatedByConsumer(PhaseInfo phaseInfo, + PhaseState newState, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws PersistenceException, SIFException + { + logger.debug("Phase State Updated called by Consumer Job for Functional Service "+getMultiObjectClassInfo().getObjectName()+" and jobID = '"+phaseInfo.getJobID()+"'. Phase to update is "+phaseInfo.getPhaseName()+". New State is "+newState.name()); + + // Generally the implementation would check here what needs to be done after such an update. + // This could cause many actions to be triggered. Some include the various updateXYZ() methods that are available as part + // of this provider class. + + // Below is just a potential example. + if (phaseInfo.getPhaseName().equalsIgnoreCase("oldYearEnrolment")) + { + if (newState == PhaseState.PENDING) + { + updateJobStateAndPhaseState(phaseInfo.getJobID(), JobState.INPROGRESS, "oldYearEnrolment", PhaseState.INPROGRESS); + } + + if (newState == PhaseState.COMPLETED) + { + // First phase is done. Mark the next phase as in progress. + updatePhaseState(phaseInfo.getJobID(), "newYearEnrolment", PhaseState.INPROGRESS); + } + } + else if ((phaseInfo.getPhaseName().equalsIgnoreCase("newYearEnrolment"))) + { + if (newState == PhaseState.COMPLETED) + { + // Mark the job as complete + updateJobState(phaseInfo.getJobID(), JobState.COMPLETED); + } + if (newState == PhaseState.FAILED) + { + // Mark the job as complete + updateJobState(phaseInfo.getJobID(), JobState.FAILED); + } + } + } + + /*--------------------------------------------*/ + /*-- Other required housekeeping Operations --*/ + /*--------------------------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.DataModelLink#getMultiObjectClassInfo() + */ + @Override + public ModelObjectInfo getMultiObjectClassInfo() + { + //This must be the same name as used in the URL or as listed in the SIF3_JOB_TEMPLATE.JOB_URL_NAME column! + return new ModelObjectInfo("RolloverStudents", null); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.provider.CoreProvider#shutdown() + */ + @Override + public void shutdown() + { + logger.debug("Shutdown Functional Service for " + getPrettyName()); + } + + /* (non-Javadoc) + * @see sif3.infra.rest.provider.CoreProvider#getCustomServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + */ + @Override + public HeaderProperties getCustomServiceInfo(SIFZone zone, + SIFContext context, + PagingInfo pagingInfo, + RequestMetadata metadata) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException + { + //We can return some specific headers. Most of the time that is really nothing but there might be times where some are required. + //For pure illustration purpose we return some values. + + HeaderProperties someHeaders = new HeaderProperties(); + someHeaders.setHeaderProperty("DemoJobProvider", getMultiObjectClassInfo().getObjectName()); + return someHeaders; + } + + /* + * Example how a specific provider can overwrite the valid job end states for a functional service. + */ + @Override + public boolean isJobEndState(JobState state) + { + // In this case we only consider the "COMPLETED" as an end state. By default COMPLETED and FAILED are end states or + // values in the provider properties called job.endStates are end states. + return state == JobState.COMPLETED; + } +} diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/client/TestEventClient.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/client/TestEventClient.java index 88f99760..9381b010 100644 --- a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/client/TestEventClient.java +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/client/TestEventClient.java @@ -30,6 +30,7 @@ import sif3.common.exception.ServiceInvokationException; import sif3.common.header.HeaderProperties; import sif3.common.header.HeaderValues.EventAction; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.RequestHeaderConstants; import sif3.common.model.SIFEvent; import sif3.infra.common.env.mgr.BrokeredProviderEnvironmentManager; @@ -146,7 +147,7 @@ private EventClient getEventClient() private void sendEvent(EventClient evtClt) throws ServiceInvokationException { - System.out.println(evtClt.sendEvents(getEvents(), null, null, getOverrideHeaderProperties())); + System.out.println(evtClt.sendEvents(getEvents(), null, null, ServiceType.OBJECT, getOverrideHeaderProperties())); } diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestConsumerLoader.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestConsumerLoader.java index 5240ebd1..1c3a9ae9 100644 --- a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestConsumerLoader.java +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestConsumerLoader.java @@ -63,10 +63,27 @@ public void stopService(String serviceID) // System.out.println(serviceID+" is running (Press Ctrl-C to stop)"); // } + private void doWait(long waitTime) + { + try + { + System.out.println("Wait for "+waitTime +" seconds..."); + Object semaphore = new Object(); + synchronized (semaphore) + { + semaphore.wait(waitTime * 1000); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } public static void main(String[] args) { System.out.println("Start Testing TestConsumerLoader..."); + TestConsumerLoader tester = new TestConsumerLoader(); if (ConsumerLoader.initialise(CONSUMER_ID)) { @@ -74,6 +91,9 @@ public static void main(String[] args) } // Put this agent to a blocking wait..... + long waitTime = 20; // seconds +// tester.doWait(waitTime); // to test shutdown procedure + try { Object semaphore = new Object(); @@ -90,6 +110,8 @@ public static void main(String[] args) } ConsumerLoader.shutdown(); + System.out.println("End Testing TestConsumerLoader."); +// System.exit(0); } } diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestRolloverStudentConsumer.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestRolloverStudentConsumer.java new file mode 100644 index 00000000..ea836e4f --- /dev/null +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestRolloverStudentConsumer.java @@ -0,0 +1,562 @@ +/* + * TestRolloverStudentConsumer.java + * Created: 26 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.test.infra.rest.consumer; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.core.MediaType; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.output.DOMOutputter; + +import au.com.systemic.framework.utils.FileReaderWriter; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.model.AttributeValue; +import sif3.common.model.CustomParameters; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.URLQueryParameter; +import sif3.common.model.ZoneContextInfo; +import sif3.common.model.job.JobCreateRequestParameter; +import sif3.common.model.job.PhaseInfo; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; +import sif3.infra.common.model.StateCollectionType; +import sif3.infra.common.model.StateType; +import sif3.infra.rest.consumer.ConsumerLoader; +import systemic.sif3.demo.rest.consumer.functional.RolloverStudentConsumer; + +/** + * @author Joerg Huber + * + */ +public class TestRolloverStudentConsumer +{ + private final static String PATH = "C:/Development/GitHubRepositories/SIF3InfraRest/SIF3InfraREST"; + + private static final String BASE_PATH = "/Development/GitHubRepositories/SIF3InfraRest/SIF3InfraREST/TestData/xml/input"; + private static final String CREATE_PHASE_PAYLOAD = BASE_PATH+"/CreatePhasePayload.xml"; + private static final String UPDATE_PHASE_PAYLOAD = BASE_PATH+"/CreatePhasePayload.xml"; + private static final String DELETE_PHASE_PAYLOAD = BASE_PATH+"/DeletePhasePayload.xml"; + + private static final String JOB_ID = "17c302dc-2a63-4af4-b020-bec34cacd2df"; + private static final String CONSUMER_ID = "StudentConsumer"; +// private static final String CONSUMER_ID = "HITSStudentConsumer"; +// private static final String CONSUMER_ID = "BrokeredAttTrackerConsumer"; +// private static final String CONSUMER_ID = "QueueTestConsumer"; + + + private static final RequestType REQUEST_TYPE = RequestType.IMMEDIATE; + + private RolloverStudentConsumer getConsumer() + { + return new RolloverStudentConsumer(); + } + + private String getPhasePayload(String payloadFile) + { + return new String(FileReaderWriter.getFileBytes(payloadFile)); + } + + private void printResponses(List responses) + { + if (responses != null) + { + for (Response response : responses) + { + printResponse(response); + } + } + else + { + System.out.println("Responses from attempt to execute a Functional Service: null"); + } + } + + private void printResponse(Response response) + { + try + { + if (response != null) + { + System.out.println("Response:\n"+response); + if (response.hasError()) + { + System.out.println("Error for Response: "+response.getError()); + } + else // We may have data + { + if (response.getHasEntity()) + { + // Check if Job or Phase response + if (response.getDataObjectType() == String.class) // This is for phase response + { + System.out.println("Response is a Phase Operation Response: "+response.getDataObject()); + } + else if ((response.getDataObjectType() == JobType.class) || (response.getDataObjectType() == JobCollectionType.class)) // Job Response + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println("Job Response Object Data:\n"+marshaller.marshalToXML(response.getDataObject())); + } + else if ((response.getDataObjectType() == StateType.class) || (response.getDataObjectType() == StateCollectionType.class)) // Phase State Response + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println("Job Phase State Response Object Data:\n"+marshaller.marshalToXML(response.getDataObject())); + } + else + { + System.out.println("Data Type retrieved in Response is not expected! Cannot unmarshal"); + } + } + else // in delayed we may have delayed receipt + { + System.out.println("No Data Returned. Response Status = "+response.getStatus()+" ("+response.getStatusMessage()+")"); + System.out.println("Delayed Receipt: "+response.getDelayedReceipt()); + } + } + } + else + { + System.out.println("Responses from attempt to execute a Functional Service: null"); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + + private org.w3c.dom.Element createInitPayload() + { + // w3c usage + DOMOutputter domOutputter = new DOMOutputter(); + try + { + Document document = new Document(); + document.setRootElement(new Element("payload")); + org.w3c.dom.Document domDocument = domOutputter.output(document); + org.w3c.dom.Element rootElement = domDocument.getDocumentElement(); + rootElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "type", "propertiesType"); + + org.w3c.dom.Element child = domDocument.createElement("property"); + child.setAttribute("name", "contextId"); + child.setTextContent("future"); + rootElement.appendChild(child); + + child = domDocument.createElement("property"); + child.setAttribute("name", "initiator"); + child.setTextContent("user1"); + rootElement.appendChild(child); + + return rootElement; + } + catch (Exception ex) + { + ex.printStackTrace(); + return null; + } + } + + private void createJobs(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Create Jobs' ..."); + try + { + HeaderProperties hdrProps = new HeaderProperties(); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTH_TOKEN, AUTH_TOKEN); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + List zoneCtxList = new ArrayList(); +// zoneCtxList.add(new ZoneContextInfo(new SIFZone("auRolloverTestingZone"), null)); + + ArrayList jobInits = new ArrayList(); + jobInits.add(new JobCreateRequestParameter("oldYearEnrolment", null)); + jobInits.add(new JobCreateRequestParameter("newYearEnrolment", null)); + jobInits.add(new JobCreateRequestParameter(null, null)); + jobInits.add(null); + + CustomParameters customParameters = new CustomParameters(); + customParameters.setHttpHeaderParams(hdrProps); + customParameters.setQueryParams(urlQueryParams); + + List> responses = consumer.createJobs(jobInits, zoneCtxList, REQUEST_TYPE, customParameters); + + System.out.println("Responses from attempt to Create Jobs:"); + if (responses != null) + { + int i = 1; + for (BulkOperationResponse response : responses) + { + System.out.println("Response "+i+":\n"+response); + if (response.hasError()) + { + System.out.println("Error for Response "+i+": "+response.getError()); + } + else // We should have a status list object or a delayed response + { + System.out.println("Job Response "+i+": "+response.getOperationStatuses()); + } + i++; + } + } + else + { + System.out.println("Responses from attempt to create Jobs: null"); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + + System.out.println("Finished 'Create Jobs'."); + } + + private void createJob(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Create Job' ..."); + try + { + List zoneCtxList = new ArrayList(); +// zoneCtxList.add(new ZoneContextInfo(new SIFZone("auRolloverTestingZone"), null)); + +// ArrayList attrValue = new ArrayList(); +// attrValue.add(new AttributeValue("old-year", "2017")); +// attrValue.add(new AttributeValue("new-year", "2018")); + JobCreateRequestParameter jobCreateInfo = new JobCreateRequestParameter("oldYearEnrolment", createInitPayload()); + + List responses = consumer.createJob(jobCreateInfo, zoneCtxList); + + System.out.println("Responses from attempt to Create Job:"); + printResponses(responses); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Create Job'."); + } + + private void getJob(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Get Job' ..."); + try + { + List zoneCtxList = new ArrayList(); + zoneCtxList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); // Default zone and context + zoneCtxList.add(new ZoneContextInfo(new SIFZone("auRolloverTestingZone"), null)); + + List responses = consumer.retrievByJobId(JOB_ID, zoneCtxList); + + System.out.println("Responses from attempt to Get Job:"); + printResponses(responses); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Get Job'."); + } + + private void getJobs(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Get Jobs' ..."); + try + { + List zoneCtxList = new ArrayList(); +// zoneCtxList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); // Default zone and context +// zoneCtxList.add(new ZoneContextInfo(new SIFZone("auRolloverTestingZone"), null)); + + List responses = consumer.retrieveJobs(new PagingInfo(5, 1), zoneCtxList, REQUEST_TYPE, null); + + System.out.println("Responses from attempt to Get Jobs:"); + printResponses(responses); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Get Jobs'."); + } + + private void removeJob(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Remove Job' ..."); + try + { + List zoneCtxList = new ArrayList(); + List responses = consumer.deleteJob(JOB_ID, zoneCtxList); + printResponses(responses); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Remove Job'."); + } + + private void removeJobs(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Remove Jobs' ..."); + try + { + HeaderProperties hdrProps = new HeaderProperties(); + hdrProps.setHeaderProperty("FS_TEST", "Test Header Value"); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + urlQueryParams.setQueryParam("FSQeryParam1", "fsValue1"); + + CustomParameters customParameters = new CustomParameters(); + customParameters.setHttpHeaderParams(hdrProps); + customParameters.setQueryParams(urlQueryParams); + + List zoneCtxList = new ArrayList(); + + ArrayList jobIDs = new ArrayList(); + jobIDs.add("98eae51c-fdec-412c-b6e9-9750223301ea"); + jobIDs.add("eeeae51c-fdec-412c-b6e9-9750223301ea"); + jobIDs.add("40dcaafb-69d4-490d-9093-e679ecbead96"); + + List> responses = consumer.deleteJobs(jobIDs, zoneCtxList, REQUEST_TYPE, customParameters); + if (responses != null) + { + int i = 1; + for (BulkOperationResponse response : responses) + { + System.out.println("Response " + i + ":\n" + response); + i++; + } + } + else + { + System.out.println("Responses from attempt to delete jobs: null"); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Remove Jobs'."); + } + + private void getServiceInfo(RolloverStudentConsumer consumer) + { + System.out.println("Start 'Get Service Info' ..."); + try + { + List responses = consumer.getServiceInfo(null, null); + printResponses(responses); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Get Service Info'."); + } + + private void updateJobPhaseState(RolloverStudentConsumer consumer, String phaseName, PhaseState state) + { + System.out.println("Start 'Update Phase State' ..."); + try + { + Response response = consumer.updatePhaseState(new PhaseInfo(JOB_ID, phaseName), state, null, null, null); + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Update Phase State'."); + } + + private void getJobPhaseStates(RolloverStudentConsumer consumer, String phaseName) + { + System.out.println("Start 'Get Phase States' ..."); + try + { + Response response = consumer.getPhaseStates(new PhaseInfo(JOB_ID, phaseName), null, null, null); + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Get Phase States'."); + } + + private void retriveFromPhase(RolloverStudentConsumer consumer, String phaseName) + { + System.out.println("Start 'Retrieve Data from Phase' ..."); + try + { + HeaderProperties hdrProps = new HeaderProperties(); + hdrProps.setHeaderProperty("FS_TEST", "Test Header Value"); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + urlQueryParams.setQueryParam("FSQeryParam1", "fsValue1"); + + CustomParameters customParameters = new CustomParameters(); + customParameters.setHttpHeaderParams(hdrProps); + customParameters.setQueryParams(urlQueryParams); + + Response response = consumer.retrieveDataFromPhase(new PhaseInfo(JOB_ID, phaseName), MediaType.APPLICATION_XML_TYPE, new PagingInfo(5, 2), null, null, null, REQUEST_TYPE, customParameters); + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Retrieve Data from Phase'."); + } + + private void createDataInPhase(RolloverStudentConsumer consumer, String phaseName) + { + System.out.println("Start 'Create Data in Phase' ..."); + try + { + String payload = getPhasePayload(CREATE_PHASE_PAYLOAD); + Response response = consumer.createDataInPhase(new PhaseInfo(JOB_ID, phaseName), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, false, null, null, REQUEST_TYPE, null); + + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Create Data in Phase'."); + } + + private void updateDataInPhase(RolloverStudentConsumer consumer, String phaseName) + { + System.out.println("Start 'Update Data in Phase' ..."); + try + { + HeaderProperties hdrProps = new HeaderProperties(); + hdrProps.setHeaderProperty("FS_TEST_UPDATE", "Test Header Value fur UPDATE PAHSE"); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + urlQueryParams.setQueryParam("FSQeryParam1", "updatePhase"); + + CustomParameters customParameters = new CustomParameters(); + customParameters.setHttpHeaderParams(hdrProps); + customParameters.setQueryParams(urlQueryParams); + + String payload = getPhasePayload(UPDATE_PHASE_PAYLOAD); + Response response = consumer.updateDataInPhase(new PhaseInfo(JOB_ID, phaseName), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, null, null, REQUEST_TYPE, customParameters); + + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Update Data in Phase'."); + } + + private void deleteDataInPhase(RolloverStudentConsumer consumer, String phaseName) + { + System.out.println("Start 'Delete Data in Phase' ..."); + try + { + String payload = getPhasePayload(DELETE_PHASE_PAYLOAD); + Response response = consumer.deleteDataInPhase(new PhaseInfo(JOB_ID, phaseName), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, null, null, REQUEST_TYPE, null); + printResponse(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("Finished 'Delete Data in Phase'."); + } + + public static void main(String[] args) + { + TestRolloverStudentConsumer tester = new TestRolloverStudentConsumer(); + System.out.println("Start Testing RolloverStudentConsumer..."); + + if (ConsumerLoader.initialise(CONSUMER_ID)) + { + System.out.println("Consumer loaded successfully. Environment Data:\n"+ConsumerEnvironmentManager.getInstance().getEnvironmentInfo()); + + RolloverStudentConsumer consumer = tester.getConsumer(); + + // + // Job Operations + // +// tester.createJob(consumer); +// tester.createJobs(consumer); + + +// tester.getJob(consumer); +// tester.getJobs(consumer); + +// tester.removeJob(consumer); +// tester.removeJobs(consumer); +// tester.getServiceInfo(consumer); + + // + // Phase State Operations + // + tester.updateJobPhaseState(consumer, "oldYearEnrolment", PhaseState.PENDING); +// tester.getJobPhaseStates(consumer, "newYearEnrolment"); + + // + // Phase Operations + // +// tester.retriveFromPhase(consumer, "oldYearEnrolment"); +// tester.createDataInPhase(consumer, "oldYearEnrolment"); +// tester.updateDataInPhase(consumer, "oldYearEnrolment"); +// tester.deleteDataInPhase(consumer, "oldYearEnrolment"); + + // Put this agent to a blocking wait..... + if (true) + { + try + { + Object semaphore = new Object(); + synchronized (semaphore) // this will block until CTRL-C is pressed. + { + System.out.println("==================================================\nTestRolloverStudentConsumer is running (Press Ctrl-C to stop)\n=================================================="); + semaphore.wait(); + } + } + catch (Exception ex) + { + System.out.println("Blocking wait in TestRolloverStudentConsumer interrupted: " + ex.getMessage()); + ex.printStackTrace(); + } + } + + System.out.println("Finalise Consumer (i.e. disconnect and remove environment)."); + consumer.finalise(); + } + + ConsumerLoader.shutdown(); + System.out.println("End Testing RolloverStudentConsumer."); + } +} diff --git a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestStudentPersonalConsumer.java b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestStudentPersonalConsumer.java index 2a49e059..152f5b1a 100644 --- a/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestStudentPersonalConsumer.java +++ b/SIF3InfraREST/SIF3Demo/sif3-demo-web/src/test/java/sif3/test/infra/rest/consumer/TestStudentPersonalConsumer.java @@ -485,10 +485,10 @@ private void getServiceInfo(StudentPersonalConsumer consumer, boolean printRepso // envZoneCtxList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); // envZoneCtxList.add(new ZoneContextInfo((SIFZone)null, new SIFContext("secure"))); - List responses = consumer.getServiceInfo(new PagingInfo(10, 0), envZoneCtxList, params); + List responses = consumer.getServiceInfo(new PagingInfo(10, 1), envZoneCtxList, params); if (printRepsonse) { - System.out.println("Responses from attempt to Get All Students:"); + System.out.println("Responses from attempt to Get StudentPersonal Service Info:"); printResponses(responses, consumer); } } @@ -514,7 +514,7 @@ public static void main(String[] args) StudentPersonalConsumer consumer = tester.getConsumer(); // tester.getStudent(consumer); - tester.getStudents(consumer, true); +// tester.getStudents(consumer, true); // tester.getStudentsByServicePath("SchoolInfos", "24ed508e1ed04bba82198233efa55859", consumer); // tester.getStudentsByServicePath("TeachingGroups", "64A309DA063A2E35B359D75101A8C3D1", consumer); // tester.getStudentsByServicePath("RoomInfos", "24ed508e1ed04bba82198233efa55859", consumer); diff --git a/SIF3InfraREST/SIF3REST/pom.xml b/SIF3InfraREST/SIF3REST/pom.xml index d3691350..22978f2b 100644 --- a/SIF3InfraREST/SIF3REST/pom.xml +++ b/SIF3InfraREST/SIF3REST/pom.xml @@ -7,7 +7,7 @@ sif3.framework sif3-framework - 0.12.0-beta + 0.13.0-beta @@ -66,6 +66,11 @@ org.slf4j slf4j-log4j12 + + + org.quartz-scheduler + quartz + @@ -82,6 +87,7 @@ ${project.version} --> + javax.servlet javax.servlet-api diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/BaseClient.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/BaseClient.java index c732afbd..1426bdf2 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/BaseClient.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/BaseClient.java @@ -21,6 +21,7 @@ import java.net.URI; import java.util.Date; import java.util.HashMap; +import java.util.Map; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -48,11 +49,14 @@ import sif3.common.header.HeaderValues; import sif3.common.header.HeaderValues.MessageType; import sif3.common.header.HeaderValues.RequestType; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.RequestHeaderConstants; import sif3.common.header.ResponseHeaderConstants; import sif3.common.model.AuthenticationInfo; +import sif3.common.model.PagingInfo; import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; import sif3.common.model.URLQueryParameter; import sif3.common.model.delayed.DelayedRequestReceipt; import sif3.common.model.security.TokenCoreInfo; @@ -63,8 +67,12 @@ import sif3.common.security.SecurityServiceFactory; import sif3.common.utils.UUIDGenerator; import sif3.common.ws.BaseResponse; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; import sif3.common.ws.ErrorDetails; +import sif3.common.ws.OperationStatus; import sif3.common.ws.Response; +import sif3.common.ws.model.MultiOperationStatusList; import sif3.infra.common.conversion.InfraMarshalFactory; import sif3.infra.common.conversion.InfraUnmarshalFactory; import sif3.infra.common.env.types.EnvironmentInfo; @@ -317,34 +325,79 @@ private void setClientEnvMgr(ClientEnvironmentManager clientEnvMgr) /*-----------------------*/ protected WebResource buildURI(WebResource svc, String relURI, String resourceID, SIFZone zone, SIFContext ctx, URLQueryParameter urlQueryParams) { - UriBuilder uriBuilder = svc.getUriBuilder(); - if (StringUtils.notEmpty(relURI)) - { - uriBuilder.path(relURI); - } - if (StringUtils.notEmpty(resourceID)) - { - uriBuilder.path(resourceID); - } - if ((zone != null) && (StringUtils.notEmpty(zone.getId()))) - { - uriBuilder.matrixParam("zoneId", zone.getId()); - } - if ((ctx != null) && (StringUtils.notEmpty(ctx.getId()))) - { - uriBuilder.matrixParam("contextId", ctx.getId()); - } - - //Add custom URL Query Parameters. - if ((urlQueryParams != null) && (urlQueryParams.getQueryParams() != null)) - { - for (String paramName : urlQueryParams.getQueryParams().keySet()) - { - uriBuilder = uriBuilder.queryParam(paramName, urlQueryParams.getQueryParam(paramName)); - } - } + return buildURI(svc, zone, ctx, urlQueryParams, relURI, resourceID); +// UriBuilder uriBuilder = svc.getUriBuilder(); +// if (StringUtils.notEmpty(relURI)) +// { +// uriBuilder.path(relURI); +// } +// if (StringUtils.notEmpty(resourceID)) +// { +// uriBuilder.path(resourceID); +// } +// if ((zone != null) && (StringUtils.notEmpty(zone.getId()))) +// { +// uriBuilder.matrixParam("zoneId", zone.getId()); +// } +// if ((ctx != null) && (StringUtils.notEmpty(ctx.getId()))) +// { +// uriBuilder.matrixParam("contextId", ctx.getId()); +// } +// +// //Add custom URL Query Parameters. +// if ((urlQueryParams != null) && (urlQueryParams.getQueryParams() != null)) +// { +// for (String paramName : urlQueryParams.getQueryParams().keySet()) +// { +// uriBuilder = uriBuilder.queryParam(paramName, urlQueryParams.getQueryParam(paramName)); +// } +// } +// +// return svc.uri(uriBuilder.build()); + } + + protected WebResource buildURI(WebResource svc, SIFZone zone, SIFContext ctx, URLQueryParameter urlQueryParams, String... uriSegments) + { + UriBuilder uriBuilder = svc.getUriBuilder(); + addPathSegments(uriBuilder, uriSegments); + if ((zone != null) && (StringUtils.notEmpty(zone.getId()))) + { + uriBuilder.matrixParam("zoneId", zone.getId()); + } + if ((ctx != null) && (StringUtils.notEmpty(ctx.getId()))) + { + uriBuilder.matrixParam("contextId", ctx.getId()); + } + + //Add custom URL Query Parameters. + if ((urlQueryParams != null) && (urlQueryParams.getQueryParams() != null)) + { + for (String paramName : urlQueryParams.getQueryParams().keySet()) + { + uriBuilder = uriBuilder.queryParam(paramName, urlQueryParams.getQueryParam(paramName)); + } + } - return svc.uri(uriBuilder.build()); + return svc.uri(uriBuilder.build()); + } + + /** + * This method will add each element of the segments list to the URI. If a segment is null or empty it will be ignored. + * After this call the uriBuilder will contain the full path of the URI made of all the segments. + * + * @param uriBuilder The uriBuilder to be built up with the segments. + * @param segments The segments to be added to the URI. + * + */ + protected void addPathSegments(UriBuilder uriBuilder, String... segments) + { + for (String segment : segments) + { + if (StringUtils.notEmpty(segment)) + { + uriBuilder.path(segment); + } + } } protected WebResource buildURI(WebResource svc, String relURI) @@ -352,12 +405,133 @@ protected WebResource buildURI(WebResource svc, String relURI) return buildURI(svc, relURI, null, null, null, null); } - + + /** + * This method will set the valid/accepted media type for this service. It will then add all header properties given to this + * method to the service. Further will also add some "default" header properties such MessageID, timestamp if it is not set yet, and + * if required a request ID. For it to be a valid SIF3 service it is expected that the authentication token is already part + * of the given header properties. It WON'T be added as part of this method. It is expected that the request and response + * mime type parameters are in its correct form. They will not be modified as used as given (i.e. no character encoding + * will be added). If they are null though then the framework's request and response mime type will be used as set + * in consumer properties file. It will also add any character encoding information to the mime types as set by the framework's + * consumer properties file. + * + * @param service The service to which media type and header properties shall be added. + * @param requestMediaType The mime type of the request. If it is not provided it will default to the + * internally determined value given by the getRequestMediaType() method and potential + * character encoding might be added. + * @param responseMediaType The mime type of the response. If it is not provided it will default to the + * internally determined value given by the getResponseMediaType() method and potential + * character encoding might be added. + * @param hdrProperties A set of defined header properties. Should really hold the authentication related properties! + * @param requestType The request type to be set in the HTTP headers. + * @param includeRequestID TRUE: Add a generated request ID header property + * @param includeFingerprint TRUE: Fingerprint will be retrieved from current session. Note for a provider this will be + * the provider's fingerprint! This is generally not desired for events. In this case + * this parameter should be set to FALSE. + * FALSE: Fingerprint from the current session will not be added to the HTTP headers. + * @hasPayload TRUE: The request will contain a payload. Required for compression header settings + * FALSE: The request is payload free. + * + * @return A builder class on which a HTTP operation can be invoked on. + */ + protected Builder setRequestHeaderAndMediaTypes(WebResource service, + MediaType requestMediaType, + MediaType responseMediaType, + HeaderProperties hdrProperties, + RequestType requestType, + boolean includeRequestID, + boolean includeFingerprint, + boolean hasPayload) + { + String charEncoding = getClientEnvMgr().getEnvironmentInfo().getCharsetEncoding(); + + // Check if we have a request & response media type. If not use the framework's value otherwise use the value given. + MediaType finalRequestMediaType = requestMediaType == null ? addEncoding(getRequestMediaType(), charEncoding) : requestMediaType; + MediaType finalResponseMediaType = responseMediaType == null ? addEncoding(getResponseMediaType(), charEncoding) : responseMediaType; + + Builder builder = service.type(finalRequestMediaType).accept(finalResponseMediaType); + + // Set some specific SIF HTTP header. First ensure that we have a valid header property structure + if (hdrProperties == null) + { + hdrProperties = new HeaderProperties(); + } + + // Always set the requestId and messageId. + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_MESSAGE_ID, UUIDGenerator.getUUID()); + //builder = builder.header(RequestHeaderConstants.HDR_MESSAGE_ID, UUIDGenerator.getUUID()); + + // Set the request type. + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_REQUEST_TYPE, ((requestType == null) ? RequestType.IMMEDIATE.name() : requestType.name())); + + // Add fingerprint to HTTP Header if it is known and not yet set. Note for events this value might already be set, so we + // should not override it! This should be indicated with the includeFingerprint parameter that would be set to false! + if (includeFingerprint) + { + if (hdrProperties.getHeaderProperty(RequestHeaderConstants.HDR_FINGERPRINT) == null) + { + if ((getClientEnvMgr().getSIF3Session() != null) && (getClientEnvMgr().getSIF3Session().getFingerprint() != null)) + { + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_FINGERPRINT, getClientEnvMgr().getSIF3Session().getFingerprint()); + } + } + } + + // Sometimes the request ID is not required (i.e. events) + if (includeRequestID) + { + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_REQUEST_ID, UUIDGenerator.getUUID()); + } + + // timestamp header must be set but only if it is not set, yet. If the authentication method is SIF_HMACSHA256 + // then this property should already be set! Don't override because it is critical to the hash of the authentication token. + if (hdrProperties.getHeaderProperty(RequestHeaderConstants.HDR_DATE_TIME) == null) // not set yet + { + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_DATE_TIME, DateUtils.nowAsISO8601()); + } + + // Compression related headers + if (getUseCompression()) + { + // Request encoding + if (hasPayload) + { + hdrProperties.setHeaderProperty(HttpHeaders.CONTENT_ENCODING, HeaderValues.EncodingType.gzip.name()); + } + + //Accepted encodings for response + hdrProperties.setHeaderProperty(HttpHeaders.ACCEPT_ENCODING, HeaderValues.ACCEPT_ENCODING_ALL); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Final set of HTTP Headers to be used in request: "+hdrProperties); + } + + if ((hdrProperties != null) && (hdrProperties.getHeaderProperties() != null) && (!hdrProperties.getHeaderProperties().isEmpty())) + { + HashMap hdrMap = hdrProperties.getHeaderProperties(); + for (String hdrPropertyName : hdrMap.keySet()) + { + String hdrPropertyValue = hdrMap.get(hdrPropertyName); + if (StringUtils.notEmpty(hdrPropertyValue)) + { + builder = builder.header(hdrPropertyName, hdrPropertyValue); + } + } + } + + return builder; + } + /** * This method will set the valid/accepted media type for this service. It will then add all header properties given to this * method to the service. Further will also add some "default" header properties such MessageID, timestamp if it is not set yet, and * if required a request ID. For it to be a valid SIF3 service it is expected that the authentication token is already part - * of the given header properties. It WON'T be added as part of this method. + * of the given header properties. It WON'T be added as part of this method. It will use the request and response mime type + * as set by the framework's consumer properties file. It will also add any character encoding information to the mime types as + * set by the framework's consumer properties file. * * @param service The service to which media type and header properties shall be added. * @param hdrProperties A set of defined header properties. Should really hold the authentication related properties! @@ -374,82 +548,7 @@ protected WebResource buildURI(WebResource svc, String relURI) */ protected Builder setRequestHeaderAndMediaTypes(WebResource service, HeaderProperties hdrProperties, RequestType requestType, boolean includeRequestID, boolean includeFingerprint, boolean hasPayload) { - String charEncoding = getClientEnvMgr().getEnvironmentInfo().getCharsetEncoding(); - Builder builder = service.type(addEncoding(getRequestMediaType(), charEncoding)).accept(addEncoding(getResponseMediaType(), charEncoding)); - - // Set some specific SIF HTTP header. First ensure that we have a valid header property structure - if (hdrProperties == null) - { - hdrProperties = new HeaderProperties(); - } - - // Always set the requestId and messageId. - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_MESSAGE_ID, UUIDGenerator.getUUID()); - //builder = builder.header(RequestHeaderConstants.HDR_MESSAGE_ID, UUIDGenerator.getUUID()); - - // Set the request type. - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_REQUEST_TYPE, ((requestType == null) ? RequestType.IMMEDIATE.name() : requestType.name())); - - // Add fingerprint to HTTP Header if it is known and not yet set. Note for events this value might already be set, so we - // should not override it! This should be indicated with the includeFingerprint parameter that would be set to false! - if (includeFingerprint) - { - if (hdrProperties.getHeaderProperty(RequestHeaderConstants.HDR_FINGERPRINT) == null) - { - if ((getClientEnvMgr().getSIF3Session() != null) && (getClientEnvMgr().getSIF3Session().getFingerprint() != null)) - { - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_FINGERPRINT, getClientEnvMgr().getSIF3Session().getFingerprint()); - } - } - } - - // Sometimes the request ID is not required (i.e. events) - if (includeRequestID) - { - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_REQUEST_ID, UUIDGenerator.getUUID()); - //builder = builder.header(RequestHeaderConstants.HDR_REQUEST_ID, UUIDGenerator.getUUID()); - } - - // timestamp header must be set but only if it is not set, yet. If the authentication method is SIF_HMACSHA256 - // then this property should already be set! Don't override because it is critical to the hash of the authentication token. - if (hdrProperties.getHeaderProperty(RequestHeaderConstants.HDR_DATE_TIME) == null) // not set yet - { - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_DATE_TIME, DateUtils.nowAsISO8601()); - //builder = builder.header(RequestHeaderConstants.HDR_DATE_TIME, DateUtils.nowAsISO8601()); - } - - // Compression related headers - if (getUseCompression()) - { - // Request encoding - if (hasPayload) - { - hdrProperties.setHeaderProperty(HttpHeaders.CONTENT_ENCODING, HeaderValues.EncodingType.gzip.name()); - } - - //Accepted encodings for response - hdrProperties.setHeaderProperty(HttpHeaders.ACCEPT_ENCODING, HeaderValues.ACCEPT_ENCODING_ALL); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Final set of HTTP Headers to be used in request: "+hdrProperties); - } - - if ((hdrProperties != null) && (hdrProperties.getHeaderProperties() != null) && (!hdrProperties.getHeaderProperties().isEmpty())) - { - HashMap hdrMap = hdrProperties.getHeaderProperties(); - for (String hdrPropertyName : hdrMap.keySet()) - { - String hdrPropertyValue = hdrMap.get(hdrPropertyName); - if (StringUtils.notEmpty(hdrPropertyValue)) - { - builder = builder.header(hdrPropertyName, hdrPropertyValue); - } - } - } - - return builder; + return setRequestHeaderAndMediaTypes(service, null, null, hdrProperties, requestType, includeRequestID, includeFingerprint, hasPayload); } /** @@ -484,6 +583,27 @@ protected HeaderProperties createAuthenticationHdr(boolean isEnvCreate, SIF3Sess return hdrProps; } + + /** + * This method will add the authentication header properties to the given set of header properties. The final set of header + * properties is then returned. + * + * @hdrProperties A set of header properties to which the authentication header shall be added. If it is null then a new set of header porperties + * will be returned that only holds the authentication header values. + */ + protected HeaderProperties addAuthenticationHdrProps(HeaderProperties hdrProperties) + { + if (hdrProperties == null) + { + hdrProperties = new HeaderProperties(); + } + + // Add Authentication info to existing header properties + hdrProperties.addHeaderProperties(createAuthenticationHdr(false, null)); + + return hdrProperties; + } + protected HeaderProperties extractHeaderInfo(ClientResponse clientResponse) { @@ -498,6 +618,41 @@ protected HeaderProperties extractHeaderInfo(ClientResponse clientResponse) return hdrProps; } + protected void addPagingInfoToHeaders(PagingInfo pagingInfo, HeaderProperties hdrProperties) + { + if (pagingInfo != null) + { + Map queryParameters = pagingInfo.getRequestValues(); + for (String key : queryParameters.keySet()) + { + hdrProperties.setHeaderProperty(key, queryParameters.get(key)); + } + } + } + + protected void addDelayedInfo(HeaderProperties hdrProperties, SIFZone zone, SIFContext context, String serviceName, ServiceType serviceType, RequestType requestType) + { + if (requestType == RequestType.DELAYED) + { + ServiceInfo serviceInfo = getSIF3Session().getServiceInfoForService(zone, context, serviceName, serviceType); + if (serviceInfo != null) + { + if ((serviceInfo.getRemoteQueueInfo() != null) && (serviceInfo.getRemoteQueueInfo().getQueueID() != null)) + { + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_QUEUE_ID, serviceInfo.getRemoteQueueInfo().getQueueID()); + } + else // should not be the case if all is called properly but you never know... + { + logger.error("No SIF Queue configured environment with Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); + } + } + else // should not be the case if all is called properly but you never know... + { + logger.error("No valid service listed in environment ACL for Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); + } + } + } + /* * Convenience method for calls that do not support DELAYED request/responses. */ @@ -573,6 +728,64 @@ protected Response setResponse(WebResource service, ClientResponse clientRespons } return response; } + + protected BulkOperationResponse setCreateBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) + { + BulkOperationResponse response = new BulkOperationResponse(); + setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); + if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || + (clientResponse.getStatusInfo().getStatusCode() == Status.CREATED.getStatusCode()) || + (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || + (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) + { + if (response.getHasEntity()) + { + String payload = clientResponse.getEntity(String.class); + MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFCreateString(payload, getResponseMediaType()); + response.setError(statusList.getError()); + response.setOperationStatuses(statusList.getOperationStatuses()); + } + } + else// We are dealing with an error case. + { + setErrorResponse(response, clientResponse); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Response from REST Call:\n"+response); + } + return response; + } + + protected BulkOperationResponse setDeleteBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) + { + BulkOperationResponse response = new BulkOperationResponse(); + setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); + if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || + (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || + (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) + { + if (response.getHasEntity()) + { + String payload = clientResponse.getEntity(String.class); + MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFDeleteString(payload, getResponseMediaType()); + response.setError(statusList.getError()); + response.setOperationStatuses(statusList.getOperationStatuses()); + + } + } + else// We are dealing with an error case. + { + setErrorResponse(response, clientResponse); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Response from REST Call:\n"+response); + } + return response; + } /* * This method cannot set the serviceName and serviceType in the Delayed Response Receipt property. It must be set by the caller of this method as this diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/EventClient.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/EventClient.java index 473b19d8..35e9e3ed 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/EventClient.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/EventClient.java @@ -23,12 +23,18 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + +import au.com.systemic.framework.utils.StringUtils; import sif3.common.CommonConstants; import sif3.common.conversion.MarshalFactory; import sif3.common.exception.ServiceInvokationException; import sif3.common.header.HeaderProperties; import sif3.common.header.HeaderValues; import sif3.common.header.HeaderValues.EventAction; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.HeaderValues.UpdateType; import sif3.common.header.RequestHeaderConstants; import sif3.common.model.SIFContext; @@ -38,11 +44,6 @@ import sif3.infra.common.env.types.ConsumerEnvironment.ConnectorName; import sif3.infra.common.env.types.ProviderEnvironment; import sif3.infra.common.interfaces.ClientEnvironmentManager; -import au.com.systemic.framework.utils.StringUtils; - -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.WebResource.Builder; /** * This class implements the function(s) required to publish REST SIF3 Events to a environment provider and/or broker. The event interface @@ -112,6 +113,7 @@ public EventClient(ClientEnvironmentManager clientEnvMgr, MediaType requestMedia * @param event The event to be sent. * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * @param serviceType The service type of the event (eg. OBJECT, FUNCTIONAL etc). * @param customHdrFields Custom HTTP header fields to be added to the request. * * @return BaseResponse Object holding appropriate values and results of the call. This call won't return any data model objects, just @@ -119,7 +121,7 @@ public EventClient(ClientEnvironmentManager clientEnvMgr, MediaType requestMedia * * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. */ - public BaseResponse sendEvents(SIFEvent event, SIFZone zone, SIFContext context, HeaderProperties customHdrFields) throws ServiceInvokationException + public BaseResponse sendEvents(SIFEvent event, SIFZone zone, SIFContext context, ServiceType serviceType, HeaderProperties customHdrFields) throws ServiceInvokationException { if (allOK) // Only send events if all is fine. { @@ -143,15 +145,10 @@ public BaseResponse sendEvents(SIFEvent event, SIFZone zone, SIFContext conte { // Don't set zone & context here. They are header parameters in the case of events. String payloadStr = getDataModelMarshaller().marshal(event.getSIFObjectList(), getRequestMediaType()); - -// if (logger.isDebugEnabled()) -// { -// logger.debug("sendEvents: Payload to send:\n"+payloadStr); -// } - HeaderProperties headerProps = getEventHeaders(event.getEventAction(), event.getUpdateType(), event.getFingerprint(), zone, context, customHdrFields); + HeaderProperties headerProps = getEventHeaders(event.getEventAction(), event.getUpdateType(), event.getFingerprint(), zone, context, serviceType, customHdrFields); Builder builder = setRequestHeaderAndMediaTypes(service, headerProps, false, false, true); - logger.debug("Send "+serviceName+" Event with payload size: "+payloadStr.length()+" to Zone = "+((zone == null) ? "default" : zone.getId())+" and Context = "+((context == null) ? "DEFAULT" : context.getId())); + logger.debug("Send "+serviceName+" Event to Zone = "+((zone == null) ? "default" : zone.getId())+" and Context = "+((context == null) ? "DEFAULT" : context.getId())); ClientResponse response = builder.post(ClientResponse.class, payloadStr); logger.debug("Receive Event Response Status: "+response.getStatus()); @@ -186,7 +183,7 @@ private ProviderEnvironment getProviderEnvironment() * @param context The context for this event. Can be null. * @return */ - private HeaderProperties getEventHeaders(EventAction eventAction, UpdateType updateType, String fingerprint, SIFZone zone, SIFContext context, HeaderProperties overrideHdrFields) + private HeaderProperties getEventHeaders(EventAction eventAction, UpdateType updateType, String fingerprint, SIFZone zone, SIFContext context, ServiceType serviceType, HeaderProperties overrideHdrFields) { // Set the override header fields HeaderProperties hdrProperties = (overrideHdrFields != null) ? new HeaderProperties(overrideHdrFields.getHeaderProperties()) : new HeaderProperties(); @@ -197,7 +194,7 @@ private HeaderProperties getEventHeaders(EventAction eventAction, UpdateType upd // Add event specific properties hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_MESSAGE_TYPE, HeaderValues.MessageType.EVENT.name()); hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_EVENT_ACTION, eventAction.name()); - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, HeaderValues.ServiceType.OBJECT.name()); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, serviceType.name()); hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_NAME, serviceName); if (eventAction == EventAction.UPDATE) diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/JobClient.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/JobClient.java new file mode 100644 index 00000000..4c2c97f3 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/JobClient.java @@ -0,0 +1,902 @@ +/* + * JobClient.java + * Created: 20 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.client; + +import java.util.List; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.exception.ServiceInvokationException; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.header.RequestHeaderConstants; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.URLQueryParameter; +import sif3.common.model.job.JobCreateRequestParameter; +import sif3.common.model.job.PhaseInfo; +import sif3.common.utils.UUIDGenerator; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.env.types.ConsumerEnvironment.ConnectorName; +import sif3.infra.common.interfaces.ClientEnvironmentManager; +import sif3.infra.common.model.DeleteIdType; +import sif3.infra.common.model.DeleteRequestType; +import sif3.infra.common.model.InitializationType; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; +import sif3.infra.common.model.PhaseStateType; +import sif3.infra.common.model.StateCollectionType; +import sif3.infra.common.model.StateType; + +/** + * @author Joerg Huber + * + */ +public class JobClient extends BaseClient +{ + private String jobNamePlural = null; + private String jobNameSingular = null; + + /** + * Constructor + * + * @param clientEnvMgr Session manager to access the clients session information. + * @param jobNamePlural The name of the Job. This must match the value of an entry in the SIF3_JOB_TEMPLATE with the JOB_URL_NAME column. + * It is also the equivalent to the Plural Form of the Job Name in the Job URL. + * @param jobNameSingular The name of the Job in its singular form. Only needed for the createJob operation. + */ + public JobClient(ClientEnvironmentManager clientEnvMgr, String jobNamePlural, String jobNameSingular) + { + super(clientEnvMgr, clientEnvMgr.getEnvironmentInfo().getConnectorBaseURI(ConnectorName.servicesConnector), clientEnvMgr.getEnvironmentInfo().getMediaType(), clientEnvMgr.getEnvironmentInfo().getMediaType(), new InfraMarshalFactory(), new InfraUnmarshalFactory(), clientEnvMgr.getEnvironmentInfo().getSecureConnection(), clientEnvMgr.getEnvironmentInfo().getCompressionEnabled()); + setJobNamePlural(jobNamePlural); + setJobNameSingular(jobNameSingular); + } + + public String getJobNamePlural() + { + return jobNamePlural; + } + + public void setJobNamePlural(String jobNamePlural) + { + this.jobNamePlural = jobNamePlural; + } + + public String getJobNameSingular() + { + return jobNameSingular; + } + + public void setJobNameSingular(String jobNameSingular) + { + this.jobNameSingular = jobNameSingular; + } + + + /*----------------------------------*/ + /*-- Create Job Object Operations --*/ + /*----------------------------------*/ + + /** + * Will invoke the REST POST method. Before the call it will retrieve the job template with the name of the jobName parameter in the + * constructor of this class. It will add the appropriate values of the createJobRequest parameter to the POST request. If there are any + * errors then the ServiceInvokationException is raised and the error is logged. + * This method will create job refIDs and expects the provider to accept them. MustUseAdvisory will be set to true. The + * assigned JobID is in the createJobRequest.jobID property. + * + * @param createJobRequest Values to be used to produce a Create Job request such as HTTP headers etc. + * Values that are null are defaulted according to the SIF Specification.
+ * - initParams = null => No initialisation parameters will be set in Job Object.
+ * - initPhaseName = null => No phase name will be set in the Job Object initialisation section. This must match + * a phase name that is part of the job phases.
+ * + * @param hdrProperties Header Properties to be added to the header of the POST request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Job Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as unable to marshal the object into its media + * type, failure to invoke actual web-service etc. + */ + public Response createJob(JobCreateRequestParameter createJobRequest, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + // Let's initialise the Job Template with the given info. + JobType job = initJobData(createJobRequest); + + WebResource service = getService(); + try + { + String payloadStr = getInfraMarshaller().marshal(job, getRequestMediaType()); + if (logger.isDebugEnabled()) + { + logger.debug("createJob: Payload to send:\n"+payloadStr); + } + + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), getJobNameSingular()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, "true"); + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, true, true, true).post(ClientResponse.class, payloadStr); + + return setResponse(service, response, JobType.class, hdrProperties, zone, context, true, Status.CREATED, Status.CONFLICT); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'createJob' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method will create a collection of Job request in one HTTP request. For each element in the provided list of + * JobCreateRequestParameter one element in the job collection will be created. The final collection is then sent to the service + * connector as specified in the SIF Specification.
+ * It is assumed that all jobs are of the same type, indicated with the jobName parameter in the constructor of this class. Each job + * element follows the same defaulting as listed in the createJob() method. + * This method will create job refIDs and expects the provider to accept them. MustUseAdvisory will be set to true. + * + * @param createMultipleJobsRequest Values to be used to produce a Create Job request such as HTTP headers etc. + * Values that are null are defaulted according to the SIF Specification.
+ * - initParams = null => No initialisation parameters will be set in Job Object.
+ * - initPhaseName = null => No phase name will be set in the Job Object initialisation section. + * This must match a phase name that is part of the job phases.
+ * + * @param hdrProperties Header Properties to be added to the header of the POST request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Job Object holding appropriate values and results of the call. Note that the payload of the response is NOT + * a job object but a list of "create" responses with JobRefIds as defined in the SIF Specification. If more details + * about each job shall be retrieved then it is up to the caller of this method to call the getJob() method for each + * jobID returned in this response. + * + * @throws ServiceInvokationException Any underlying errors occurred such as unable to marshal the object into its media + * type, failure to invoke actual web-service etc. + */ + public BulkOperationResponse createJobs(List createMultipleJobsRequest, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context, RequestType requestType) throws ServiceInvokationException + { + // Let's iterate through the jobs... + JobCollectionType jobs = new JobCollectionType(); + for (JobCreateRequestParameter jobParams : createMultipleJobsRequest) + { + // Let's initialise the Job Template with the given info. There is the possibility that this is null where no initialisation + // is required. We should create it and set it, so that a jobId can be assigned. + if (jobParams == null) + { + jobParams = new JobCreateRequestParameter(); + } + jobs.getJob().add(initJobData(jobParams)); + } + + WebResource service = getService(); + try + { + String payloadStr = getInfraMarshaller().marshal(jobs, getRequestMediaType()); + if (logger.isDebugEnabled()) + { + logger.debug("createJob: Payload to send:\n"+payloadStr); + } + + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, "true"); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, requestType, true, true, true).post(ClientResponse.class, payloadStr); + + return setCreateBulkResponse(service, response, zone, context, requestType, hdrProperties); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'createJobs' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /*---------------------------------*/ + /*-- Query Job Object Operations --*/ + /*---------------------------------*/ + + /** + * This method will retrieve a Job given by its jobID. This call will invoke the REST GET call. The object will + * be unmarshalled into the Job Object and be stored as part of the returned Response object. If there are any internal + * errors then a ServiceInvokationException will be raised and the error is logged. + * + * @param jobID The Id of the job to be returned. If the job doesn't exist the appropriate status is set in the returned Response object as defined + * by the SIF3 spec. + * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return The Response to this GET call. See the Response class for details of the content of this object. + * + * @throws ServiceInvokationException An internal error occurred. An error is logged. + */ + public Response getJob(String jobID, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), jobID); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, true, true, false).get(ClientResponse.class); + + return setResponse(service, response, JobType.class, hdrProperties, zone, context, true, Status.OK, Status.NOT_MODIFIED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'getJob' service (REST GET) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method will retrieve a list of Jobs in the given zone and context. It will invoke the REST GET call (Query). Because no jobID is + * provided it will automatically return a list of jobs (collection). Additional parameters of this method indicate what part of the + * collection shall be returned (pagingInfo) as well as what zone and context this query shall apply to. + * + * @param pagingInfo Page information to be set for the provider to determine which results to return. + * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values and results of the call. This is either a job collection or an error object. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response getJobs(PagingInfo pagingInfo, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context, RequestType requestType) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addPagingInfoToHeaders(pagingInfo, hdrProperties); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, requestType, true, true, false).get(ClientResponse.class); + + return setResponse(service, response, JobCollectionType.class, hdrProperties, zone, context, requestType, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'getJobs' service (REST GET) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /*----------------------------------*/ + /*-- Delete Job Object Operations --*/ + /*----------------------------------*/ + + /** + * Removes the Job with the given 'resourceID'. Before the call it will add the 'hdrProperties' to the header of the DELETE request. + * If there are any errors then the ServiceInvokationException is raised and the error is logged. + * + * @param jobID The Job Id of the job to be deleted. If the job doesn't exist the appropriate status is set in the + * returned Response object as defined by the SIF3 spec. + * @param hdrProperties Header Properties to be added to the header of the DELETE request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response removeJob(String jobID, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), jobID); + + hdrProperties = addAuthenticationHdrProps(hdrProperties); + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, true, true, false).delete(ClientResponse.class); + + return setResponse(service, response, null, hdrProperties, zone, context, true, Status.NO_CONTENT); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'removeJob' service (REST DELETE) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method removes all the Jobs given by the 'resourceIDs' parameter. This method is used to delete many jobs in one call as + * defined by the SIF3 spec. The returned list of responses equate to one response per object in the given payload.

+ * + * There is an issue with java.net.HttpURLConnection where it doesn't allow an payload for the HTTP DELETE operation. So currently + * the implementation of the removeMany fakes such a behaviour and actually calls the HTTP PUT with a HTTP Header called 'methodOverride' as + * specified in the SIF 3.x specification. + * + * @param jobIDs A list of jobs given by their IDs to be deleted. + * @param hdrProperties Header Properties to be added to the header of the DELETE request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public BulkOperationResponse removeJobs(List jobIDs, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context, RequestType requestType) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural()); + + //Convert List of resources to DeletesTypes + DeleteRequestType deleteRequest = getInfraObjectFactory().createDeleteRequestType(); + deleteRequest.setDeletes(getInfraObjectFactory().createDeleteIdCollection()); + + if (jobIDs != null) + { + for (String resourceID : jobIDs) + { + DeleteIdType id = getInfraObjectFactory().createDeleteIdType(); + id.setId(resourceID); + deleteRequest.getDeletes().getDelete().add(id); + } + } + String payloadStr = getInfraMarshaller().marshal(deleteRequest, getRequestMediaType()); + + hdrProperties = addAuthenticationHdrProps(hdrProperties); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + // Set specific header so that PUT method knows that a DELETE and not an UPDATE is required! + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_METHOD_OVERRIDE, HeaderValues.MethodType.DELETE.name()); + + if (logger.isDebugEnabled()) + { + logger.debug("removeJobs: Payload to send:\n"+payloadStr); + } + ClientResponse cltResponse = setRequestHeaderAndMediaTypes(service, hdrProperties, requestType, true, true, true).put(ClientResponse.class, payloadStr); + + return setDeleteBulkResponse(service, cltResponse, zone, context, requestType, hdrProperties); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'removeJobs' service (REST DELETE) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * Will invoke the REST HEAD call. It will be invoked on the Job service. It will not return a payload as per + * HTTP Specification of the HEAD method. It will return a number of HTTP Header fields though. These can be retrieved as + * part of the returned response object. Because this method almost mirrors the HTTP GET for the getJobs() service all + * parameters that would make up the getJobs() method in this class are supported. The exception is the requestType parameter + * that are allowed in the getJobs() method. This parameter does not make any sense for this method and is therefore omitted. + * + * @param pagingInfo Page information to be set for the provider to determine which results to return. + * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response getServiceInfo(PagingInfo pagingInfo, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addPagingInfoToHeaders(pagingInfo, hdrProperties); + + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, RequestType.IMMEDIATE, true, true, false).head(); + + return setResponse(service, response, null, hdrProperties, zone, context, RequestType.IMMEDIATE, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'getServiceInfo' Job service (REST HEAD) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /*----------------------------------------------------------------------*/ + /*-- Update Job Object Operations: NOT ALLOWED according to SIF 3.2.1 --*/ + /*----------------------------------------------------------------------*/ + + /*--------------------------*/ + /*-- Job Phase Operations --*/ + /*--------------------------*/ + /** + * This method is used to retrieve data from a given phase. This can be any data. It is up to the implementation of the + * functional service to know what that data is for a given phase. This framework is agnostic to that data. The returned + * value is a String that must represent the "marshalled" version of the data in the format indicated by the "returnMimeType". + * Because the data that can be returned as part of a phase might be a collection, the paging parameter can be provided. If the + * data to be returned is considered too large by the provider (implementation dependent) then an appropriate error is + * returned (HTTP Status 413 - Response too large). + * + * @param phaseInfo Hold the jobID and phase name of the job from where the data shall be retrieved. If the parameter or + * any of its properties must not be null/empty. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED! Might be rejected + * by provider.). + * @param hdrProperties Header Properties to be added to the header of the request. Can be null. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values and results of the call. Because the framework is agnostic to the + * data that is returned for a phase the Response.dataObjectType will be set to "String" and the Response.dataObject + * will hold the string representation of the returned payload. It is up to the caller of this method to potentially + * marshal that payload into an appropriate object. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response retrieveDataFromPhase(PhaseInfo phaseInfo, MediaType returnMimeType, PagingInfo pagingInfo, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context, RequestType requestType) + throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addPagingInfoToHeaders(pagingInfo, hdrProperties); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + ClientResponse response = setRequestHeaderAndMediaTypes(service, returnMimeType, returnMimeType, hdrProperties, requestType, true, true, false).get(ClientResponse.class); + + return setResponse(service, response, String.class, hdrProperties, zone, context, requestType, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'retrieveDataFromPhase' service (REST GET) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method should perform a "create" operation for the data given in the "data" parameter. This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). This is indicated with a HTTP status of 204 (No content). However if data + * is returned then response.dataObject will hold the data in its string representation and the response.dataObjectType will be + * set to String.class. It is expected that the consumer uses the response.mediaType to unmarshal the data to a structure suitable + * for the consumer. It is also expected that the response.mediaType would be of the same mime type as indicated by the consumer + * in the returnMimeType parameter.

+ * + * In case of a delayed request the the response.dataObject and response.dataObjectType are null and the response.delayedReceipt + * property will be populated accordingly.

+ * + * In case of a failure an appropriate error message is returned in the Response Object. If there is no error returned it is + * assumed that this method completed successfully and the appropriate HTTP status is set. + * + * @param phaseInfo Hold the jobID and phase name of the job from where the data shall be created. If the parameter or + * any of its properties must not be null/empty. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property + * of this parameter can be null in cases where no data is provided to this phase. This is + * entirely valid according to the SIF Specification. If data is provided (as a String) then + * the mime type is set as well, by the consumer, to indicate the format of the data. The + * provider can us this mime type to unmarshal the data into the appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param useAdvisory If new IDs for the created data shall be allocated or used as given. In some cases that may not be applicable + * but if it is then this parameter indicates the expected behaviour. + * @param hdrProperties Header Properties to be added to the header of the request. Can be null. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. If the status is set to 204 then no content is returned which is also a valid scenario. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response createDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + boolean useAdvisory, + HeaderProperties hdrProperties, + URLQueryParameter urlQueryParams, + SIFZone zone, + SIFContext context, + RequestType requestType) + throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, String.valueOf(useAdvisory)); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + ClientResponse response = null; + if ((phaseDataRequest != null) && (phaseDataRequest.getData() != null)) + { + response = setRequestHeaderAndMediaTypes(service, phaseDataRequest.getMimeType(), returnMimeType, hdrProperties, requestType, true, true, true).post(ClientResponse.class, phaseDataRequest.getData()); + } + else + { + response = setRequestHeaderAndMediaTypes(service, null, returnMimeType, hdrProperties, requestType, true, true, false).post(ClientResponse.class); + } + + return setResponse(service, response, String.class, hdrProperties, zone, context, requestType, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED, Status.CREATED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'createDataInPhase' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method should perform an "update" operation for the data given in the "phaseData.data" (string). This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). However if data is returned then response.dataObject will hold the data in + * its string representation and the response.dataObjectType will be set to String.class. It is expected that the consumer uses + * the response.mediaType to unmarshal the data to a structure suitable for the consumer. It is also expected that the + * response.mediaType would be of the same mime type as indicated by the consumer in the returnMimeType parameter.

+ * + * @param phaseInfo Hold the jobID and phase name of the job where the data shall be updated. If the parameter or + * any of its properties must not be null/empty. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property + * of this parameter can be null in cases where no data is provided to this phase. This is + * entirely valid according to the SIF Specification. If data is provided (as a String) then + * the mime type is set as well, by the consumer, to indicate the format of the data. The + * provider can us this mime type to unmarshal the data into the appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param hdrProperties Header Properties to be added to the header of the request. Can be null. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response updateDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + HeaderProperties hdrProperties, + URLQueryParameter urlQueryParams, + SIFZone zone, + SIFContext context, + RequestType requestType) + throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + ClientResponse response = null; + if ((phaseDataRequest != null) && (phaseDataRequest.getData() != null)) + { + response = setRequestHeaderAndMediaTypes(service, phaseDataRequest.getMimeType(), returnMimeType, hdrProperties, requestType, true, true, true).put(ClientResponse.class, phaseDataRequest.getData()); + } + else + { + response = setRequestHeaderAndMediaTypes(service, null, returnMimeType, hdrProperties, requestType, true, true, false).put(ClientResponse.class); + } + + return setResponse(service, response, String.class, hdrProperties, zone, context, requestType, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'updateDataInPhase' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method should perform a "delete" operation for the data given in the "phaseData.data" (string). This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). However if data is returned then response.dataObject will hold the data in + * its string representation and the response.dataObjectType will be set to String.class. It is expected that the consumer uses + * the response.mediaType to unmarshal the data to a structure suitable for the consumer. It is also expected that the + * response.mediaType would be of the same mime type as indicated by the consumer in the returnMimeType parameter.

+ * + * In case of a delayed request the the response.dataObject and response.dataObjectType are null and the response.delayedReceipt + * property will be populated accordingly.

+ * + * In case of a failure an appropriate error message is returned in the Response Object. If there is no error returned it is + * assumed that this method completed successfully and the appropriate HTTP status is set. + * + * @param phaseInfo Hold the jobID and phase name of the job where the data shall be deleted. If the parameter or + * any of its properties must not be null/empty. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property + * of this parameter can be null in cases where no data is provided to this phase. This is + * entirely valid according to the SIF Specification. If data is provided (as a String) then + * the mime type is set as well, by the consumer, to indicate the format of the data. The + * provider can us this mime type to unmarshal the data into the appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param hdrProperties Header Properties to be added to the header of the request. Can be null. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response deleteDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + HeaderProperties hdrProperties, + URLQueryParameter urlQueryParams, + SIFZone zone, + SIFContext context, + RequestType requestType) + throws ServiceInvokationException + { + WebResource service = getService(); + try + { + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName()); + hdrProperties = addAuthenticationHdrProps(hdrProperties); + + // Set specific header so that PUT method knows that a DELETE and not an UPDATE is required! + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_METHOD_OVERRIDE, HeaderValues.MethodType.DELETE.name()); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + addDelayedInfo(hdrProperties, zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL, requestType); + + ClientResponse response = null; + if ((phaseDataRequest != null) && (phaseDataRequest.getData() != null)) + { + response = setRequestHeaderAndMediaTypes(service, phaseDataRequest.getMimeType(), returnMimeType, hdrProperties, requestType, true, true, true).put(ClientResponse.class, phaseDataRequest.getData()); + } + else + { + response = setRequestHeaderAndMediaTypes(service, null, returnMimeType, hdrProperties, requestType, true, true, false).put(ClientResponse.class); + } + + return setResponse(service, response, String.class, hdrProperties, zone, context, requestType, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'deleteDataInPhase' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /*--------------------------------*/ + /*-- Job Phase State Operations --*/ + /*--------------------------------*/ + + /** + * This method attempts to update the state of a given phase for a job.
+ * An error message will be part of the returned Response for the following cases:
+ * - Job doesn't exist.
+ * - If the phase or state is invalid.
+ * - Consumer doesn't have appropriate permissions for this operation.
+ * - Potentially the job is no longer 'update-able' because it has reached an end state (eg. COMPLETED or FAILED).
+ * + * @param phaseInfo Hold the jobID and phase name of the job where the state shall be updated. If the parameter or + * any of its properties must not be null/empty. + * @param newState The value of the new state for the given phase. If null then no action is taken. + * @param hdrProperties Header Properties to be added to the header of the request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response updatePhaseState(PhaseInfo phaseInfo, PhaseState newState, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + // Create the phase state object + StateType newPhaseState = new StateType(); + newPhaseState.setType(PhaseStateType.valueOf(newState.name())); + String payloadStr = getInfraMarshaller().marshal(newPhaseState, getRequestMediaType()); + if (logger.isDebugEnabled()) + { + logger.debug("Phase State: Payload to send:\n"+payloadStr); + } + + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName(), "states", "state"); + + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, true, true, true).post(ClientResponse.class, payloadStr); + + return setResponse(service, response, StateType.class, hdrProperties, zone, context, true, Status.CREATED, Status.CONFLICT); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'updatePhaseState' service (REST POST) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /** + * This method attempts to retrieve the state(s) of the given phase. The returned object in the response will be of + * type StateCollectionType as defined in the infrastructure data model.
+ * An error message will be part of the returned Response for the following cases:
+ * - Job doesn't exist.
+ * - If the phase is invalid.
+ * - Consumer doesn't have appropriate permissions for this operation.
+ * + * @param phaseInfo Hold the jobID and phase name of the job where the states shall be retrieved. If the parameter or + * any of its properties must not be null/empty. + * @param hdrProperties Header Properties to be added to the header of the request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + */ + public Response getPhaseStates(PhaseInfo phaseInfo, HeaderProperties hdrProperties, URLQueryParameter urlQueryParams, SIFZone zone, SIFContext context) throws ServiceInvokationException + { + WebResource service = getService(); + try + { + // Create the phase state object + + service = buildURI(service, zone, context, urlQueryParams, getJobNamePlural(), phaseInfo.getJobID(), phaseInfo.getPhaseName(), "states"); + + hdrProperties = addAuthenticationHdrProps(hdrProperties); + hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, ServiceType.FUNCTIONAL.name()); + + ClientResponse response = setRequestHeaderAndMediaTypes(service, hdrProperties, true, true, true).get(ClientResponse.class); + + return setResponse(service, response, StateCollectionType.class, hdrProperties, zone, context, true, Status.OK, Status.NOT_MODIFIED, Status.NO_CONTENT, Status.ACCEPTED); + } + catch (Exception ex) + { + String errorMsg = "Failed to invoke 'getPhaseStates' service (REST GET) on URI " + service.getURI() + ": " + ex.getMessage(); + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg, ex); + } + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + private JobType initJobData(JobCreateRequestParameter jobInitRequest) throws ServiceInvokationException + { + // First we get the job template + JobType job = getClientEnvMgr().getJobTemplate(getJobNamePlural()); + if (job == null) // not good. No such job exists in job template store + { + String errorMsg = "No Job Template with the name " + getJobNamePlural() + " exists. Needs to be configured in appropriate tables first."; + logger.error(errorMsg); + throw new ServiceInvokationException(errorMsg); + } + + // If we get here all good. Populate initialisation parameters. + if ((jobInitRequest != null) && (StringUtils.notEmpty(jobInitRequest.getInitPhaseName()) || (jobInitRequest.getInitParams() != null))) + { + InitializationType initSection = new InitializationType(); + if (StringUtils.notEmpty(jobInitRequest.getInitPhaseName())) + { + initSection.setPhaseName(jobInitRequest.getInitPhaseName().trim()); + } + if (jobInitRequest.getInitParams() != null) + { + initSection.setPayload(jobInitRequest.getInitParams()); + } + job.setInitialization(initSection); + } + job.setId(UUIDGenerator.getUUID()); + jobInitRequest.setJobID(job.getId()); + + return job; + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/ObjectServiceClient.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/ObjectServiceClient.java index c42dab79..e20815aa 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/ObjectServiceClient.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/ObjectServiceClient.java @@ -17,11 +17,13 @@ import java.net.URI; import java.util.List; -import java.util.Map; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; + import sif3.common.conversion.MarshalFactory; import sif3.common.conversion.UnmarshalFactory; import sif3.common.exception.ServiceInvokationException; @@ -33,7 +35,6 @@ import sif3.common.model.PagingInfo; import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; -import sif3.common.model.ServiceInfo; import sif3.common.model.URLQueryParameter; import sif3.common.ws.BulkOperationResponse; import sif3.common.ws.CreateOperationStatus; @@ -44,9 +45,6 @@ import sif3.infra.common.model.DeleteIdType; import sif3.infra.common.model.DeleteRequestType; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; - /** * This class is a core client class for all Object Services and their CRUD operations (request connector) for SIF3. It takes care of * all the little things that define the SIF3 REST transport for all the Object Service operations stated in the SIF3 spec. @@ -290,6 +288,9 @@ public Response removeSingle(String relURI, String resourceID, HeaderProperties * @param serviceType Currently this should be OBJECT or SERVICPATH. * @param pagingInfo Page information to be set for the provider to determine which results to return. * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. * @param returnObjectClass The class type into which the object shall be unmarshalled into. The final object is stored in the * returned Response object. * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. @@ -334,6 +335,9 @@ public Response getMany(String relURI, String serviceName, ServiceType serviceTy * @param exampleObject The example data model object. This must be the single object type. * @param pagingInfo Page information to be set for the provider to determine which results to return. * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. * @param returnObjectClass The class type into which the object shall be unmarshalled into. The final object is stored in the * returned Response object. * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. @@ -481,9 +485,7 @@ public BulkOperationResponse updateMany(String relURI, String s /** * This invokes the REST PUT call. This method is used to delete many objects in one call as defined by the SIF3 spec. The - * returned list of responses equate to one response per object in the given payload. The order of the responses is the same as the - * order in the original payload. The first response in the BulkOperationResponse list is the response to the create of the first - * object in the payload etc.

+ * returned list of responses equate to one response per object in the given payload.

* * There is an issue with java.net.HttpURLConnection where it doesn't allow an payload for the HTTP DELETE operation. So currently * the implementation of the removeMany fakes such a behaviour and actually calls the HTTP PUT with a HTTP Header called 'methodOverride' as @@ -549,7 +551,7 @@ public BulkOperationResponse removeMany(String relURI, String s } /*-----------------------------------*/ - /*-- Get Service Infor (HTTP HEAD) --*/ + /*-- Get Service Info (HTTP HEAD) --*/ /*-----------------------------------*/ /** * Will invoke the REST HEAD call. It will not return a payload as per HTTP Specification of the HEAD method. It will @@ -563,6 +565,9 @@ public BulkOperationResponse removeMany(String relURI, String s * the service path name as in the Environment ACL (i.e. schools/{}/students). * @param pagingInfo Page information to be set for the provider to determine which results to return. * @param hdrProperties Header Properties to be added to the header of the GET request. + * @param urlQueryParams URL query parameters to be added to the request. It is assumed that these are custom + * URL query parameters. They are conveyed to the provider unchanged. URL query parameter + * names are case sensitive. This parameter can be null. * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. * @@ -591,68 +596,66 @@ public Response getServiceInfo(String relURI, String serviceName, PagingInfo pag } } - - /*---------------------*/ /*-- Private Methods --*/ /*---------------------*/ - private BulkOperationResponse setCreateBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) - { - BulkOperationResponse response = new BulkOperationResponse(); - setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); - if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || - (clientResponse.getStatusInfo().getStatusCode() == Status.CREATED.getStatusCode()) || - (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || - (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) - { - if (response.getHasEntity()) - { - String payload = clientResponse.getEntity(String.class); - MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFCreateString(payload, getResponseMediaType()); - response.setError(statusList.getError()); - response.setOperationStatuses(statusList.getOperationStatuses()); - } - } - else// We are dealing with an error case. - { - setErrorResponse(response, clientResponse); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Response from REST Call:\n"+response); - } - return response; - } - - private BulkOperationResponse setDeleteBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) - { - BulkOperationResponse response = new BulkOperationResponse(); - setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); - if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || - (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || - (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) - { - if (response.getHasEntity()) - { - String payload = clientResponse.getEntity(String.class); - MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFDeleteString(payload, getResponseMediaType()); - response.setError(statusList.getError()); - response.setOperationStatuses(statusList.getOperationStatuses()); - - } - } - else// We are dealing with an error case. - { - setErrorResponse(response, clientResponse); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Response from REST Call:\n"+response); - } - return response; - } +// private BulkOperationResponse setCreateBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) +// { +// BulkOperationResponse response = new BulkOperationResponse(); +// setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); +// if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || +// (clientResponse.getStatusInfo().getStatusCode() == Status.CREATED.getStatusCode()) || +// (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || +// (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) +// { +// if (response.getHasEntity()) +// { +// String payload = clientResponse.getEntity(String.class); +// MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFCreateString(payload, getResponseMediaType()); +// response.setError(statusList.getError()); +// response.setOperationStatuses(statusList.getOperationStatuses()); +// } +// } +// else// We are dealing with an error case. +// { +// setErrorResponse(response, clientResponse); +// } +// +// if (logger.isDebugEnabled()) +// { +// logger.debug("Response from REST Call:\n"+response); +// } +// return response; +// } + +// private BulkOperationResponse setDeleteBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) +// { +// BulkOperationResponse response = new BulkOperationResponse(); +// setBaseResponseData(response, clientResponse, requestHeaders, zone, context, true, requestType, service.getURI().toString()); +// if ((clientResponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode()) || +// (clientResponse.getStatusInfo().getStatusCode() == Status.ACCEPTED.getStatusCode()) || +// (clientResponse.getStatusInfo().getStatusCode() == Status.NO_CONTENT.getStatusCode())) +// { +// if (response.getHasEntity()) +// { +// String payload = clientResponse.getEntity(String.class); +// MultiOperationStatusList statusList = getInfraMapper().toStatusListFromSIFDeleteString(payload, getResponseMediaType()); +// response.setError(statusList.getError()); +// response.setOperationStatuses(statusList.getOperationStatuses()); +// +// } +// } +// else// We are dealing with an error case. +// { +// setErrorResponse(response, clientResponse); +// } +// +// if (logger.isDebugEnabled()) +// { +// logger.debug("Response from REST Call:\n"+response); +// } +// return response; +// } private BulkOperationResponse setUpdateBulkResponse(WebResource service, ClientResponse clientResponse, SIFZone zone, SIFContext context, RequestType requestType, HeaderProperties requestHeaders) { @@ -682,55 +685,38 @@ private BulkOperationResponse setUpdateBulkResponse(WebResource return response; } - private void addPagingInfoToHeaders(PagingInfo pagingInfo, HeaderProperties hdrProperties) - { - if (pagingInfo != null) - { - Map queryParameters = pagingInfo.getRequestValues(); - for (String key : queryParameters.keySet()) - { - hdrProperties.setHeaderProperty(key, queryParameters.get(key)); - } - } - } - - private void addDelayedInfo(HeaderProperties hdrProperties, SIFZone zone, SIFContext context, String serviceName, ServiceType serviceType, RequestType requestType) - { - if (requestType == RequestType.DELAYED) - { - ServiceInfo serviceInfo = getSIF3Session().getServiceInfoForService(zone, context, serviceName, serviceType); - if (serviceInfo != null) - { - if ((serviceInfo.getRemoteQueueInfo() != null) && (serviceInfo.getRemoteQueueInfo().getQueueID() != null)) - { - hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_QUEUE_ID, serviceInfo.getRemoteQueueInfo().getQueueID()); - } - else // should not be the case if all is called properly but you never know... - { - logger.error("No SIF Queue configured environment with Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); - } - } - else // should not be the case if all is called properly but you never know... - { - logger.error("No valid service listed in environment ACL for Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); - } - } - } - - /* - * This method will add the authentication header properties to the given set of header properties. The final set of header - * properties is then returned. - */ - private HeaderProperties addAuthenticationHdrProps(HeaderProperties hdrProperties) - { - if (hdrProperties == null) - { - hdrProperties = new HeaderProperties(); - } - - // Add Authentication info to existing header properties - hdrProperties.addHeaderProperties(createAuthenticationHdr(false, null)); - - return hdrProperties; - } +// private void addPagingInfoToHeaders(PagingInfo pagingInfo, HeaderProperties hdrProperties) +// { +// if (pagingInfo != null) +// { +// Map queryParameters = pagingInfo.getRequestValues(); +// for (String key : queryParameters.keySet()) +// { +// hdrProperties.setHeaderProperty(key, queryParameters.get(key)); +// } +// } +// } +// +// private void addDelayedInfo(HeaderProperties hdrProperties, SIFZone zone, SIFContext context, String serviceName, ServiceType serviceType, RequestType requestType) +// { +// if (requestType == RequestType.DELAYED) +// { +// ServiceInfo serviceInfo = getSIF3Session().getServiceInfoForService(zone, context, serviceName, serviceType); +// if (serviceInfo != null) +// { +// if ((serviceInfo.getRemoteQueueInfo() != null) && (serviceInfo.getRemoteQueueInfo().getQueueID() != null)) +// { +// hdrProperties.setHeaderProperty(RequestHeaderConstants.HDR_QUEUE_ID, serviceInfo.getRemoteQueueInfo().getQueueID()); +// } +// else // should not be the case if all is called properly but you never know... +// { +// logger.error("No SIF Queue configured environment with Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); +// } +// } +// else // should not be the case if all is called properly but you never know... +// { +// logger.error("No valid service listed in environment ACL for Service Name = "+serviceName+", Service Type = "+serviceType+", Zone = "+zone.getId()+" and Context = "+context.getId()); +// } +// } +// } } \ No newline at end of file diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/SubscriptionClient.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/SubscriptionClient.java index 7a0ab012..0bdbb8a3 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/SubscriptionClient.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/client/SubscriptionClient.java @@ -113,7 +113,7 @@ public Response subscribe(SubscriptionType subscriptionInfo) throws ServiceInvok } if (StringUtils.isEmpty(subscriptionInfo.getServiceType())) { - throw new IllegalArgumentException("Property serviceType in subscriptioninfo for method subscribe is empty or null. Must be of value 'OBJECT', 'FUNCTION' or 'UTILITY'"); + throw new IllegalArgumentException("Property serviceType in subscriptioninfo for method subscribe is empty or null. Must be of value 'OBJECT', 'FUNCTIONAL' or 'UTILITY'"); } // OK all good now... diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractConsumer.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractConsumer.java index dcc1c6fb..67ec9cd6 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractConsumer.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractConsumer.java @@ -18,17 +18,10 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import au.com.systemic.framework.utils.AdvancedProperties; -import au.com.systemic.framework.utils.StringUtils; import au.com.systemic.framework.utils.Timer; import sif3.common.exception.PersistenceException; import sif3.common.exception.ServiceInvokationException; @@ -43,6 +36,8 @@ import sif3.common.interfaces.Consumer; import sif3.common.interfaces.DelayedConsumer; import sif3.common.interfaces.QueryConsumer; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.CustomParameters; import sif3.common.model.PagingInfo; import sif3.common.model.QueryCriteria; @@ -50,14 +45,9 @@ import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; import sif3.common.model.ServiceInfo; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; import sif3.common.model.URLQueryParameter; import sif3.common.model.ZoneContextInfo; -import sif3.common.model.delayed.DelayedRequestReceipt; import sif3.common.model.delayed.DelayedResponseReceipt; -import sif3.common.persist.model.SIF3Session; -import sif3.common.ws.BaseResponse; import sif3.common.ws.BulkOperationResponse; import sif3.common.ws.CreateOperationStatus; import sif3.common.ws.ErrorDetails; @@ -67,8 +57,6 @@ import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; import sif3.infra.common.env.types.ConsumerEnvironment; import sif3.infra.rest.client.ObjectServiceClient; -import sif3.infra.rest.queue.LocalConsumerQueue; -import sif3.infra.rest.queue.LocalMessageConsumer; /** * This is the core class that a developer will use to implement their consumers. Each consumer for each object type MUST extend this @@ -83,30 +71,30 @@ * * @author Joerg Huber */ -public abstract class AbstractConsumer implements Consumer, DelayedConsumer, QueryConsumer +public abstract class AbstractConsumer extends BaseConsumer implements Consumer, DelayedConsumer, QueryConsumer { protected final Logger logger = LoggerFactory.getLogger(getClass()); /* Below variables are for testing purposes only */ - private static Boolean testMode = null; +// private static Boolean testMode = null; /* End Testing variables */ - private boolean checkACL = true; +// private boolean checkACL = true; // private boolean initOK = true; /* The next two properties are used for delayed responses or events */ - private LocalConsumerQueue localConsumerQueue = null; - private ExecutorService service = null; +// private LocalConsumerQueue localConsumerQueue = null; +// private ExecutorService service = null; /*-------------------------------------------------------------*/ /* Abstract method relating to general Consumer functionality. */ /*-------------------------------------------------------------*/ - /** - * This method is called when a consumer service is shut down. It can be used to free up internally allocated resources - * as well as clean-up other things. - */ - public abstract void shutdown(); +// /** +// * This method is called when a consumer service is shut down. It can be used to free up internally allocated resources +// * as well as clean-up other things. +// */ +// public abstract void shutdown(); /*---------------------------------------------------------------------*/ /* Abstract method relating to DELAYED request response functionality. */ @@ -165,16 +153,6 @@ public abstract class AbstractConsumer implements Consumer, DelayedConsumer, Que */ public abstract void processDelayedServicePath(Object dataObject, QueryCriteria queryCriteria, PagingInfo pagingInfo, DelayedResponseReceipt receipt); - /** - * This method is called when a delayed error response is retrieved from the consumer's queue.

- * - * @see sif3.common.interfaces.DelayedConsumer#onError(sif3.common.ws.ErrorDetails, sif3.common.model.delayed.DelayedResponseReceipt) - * - * @param error See onError() method in DelayedConsumer class. - * @param receipt See onError() method in DelayedConsumer class. - */ - public abstract void processDelayedError(ErrorDetails error, DelayedResponseReceipt receipt); - /*----------------------------------*/ /* Consumer Implementation Methods. */ /*----------------------------------*/ @@ -186,41 +164,18 @@ public AbstractConsumer() { super(); - // Set some properties at this stage for simplicity reasons. - checkACL = getConsumerEnvironment().getCheckACL(); - -/* - //Check a few things to ensure that all core methods are implemented. - if (getMarshaller() == null) - { - logger.error("Consumer "+getConsumerName()+" has not implemented the getMarshaller() method properly. It returns null which is not valid."); - initOK = false; - } - if (getUnmarshaller() == null) - { - logger.error("Consumer "+getConsumerName()+" has not implemented the getUnmarshaller() method properly. It returns null which is not valid."); - initOK = false; - } - if (getSingleObjectClassInfo() == null) - { - logger.error("Consumer "+getConsumerName()+" has not implemented the getSingleObjectClassInfo() method properly. It returns null which is not valid."); - initOK = false; - } - if (getMultiObjectClassInfo() == null) - { - logger.error("Consumer "+getConsumerName()+" has not implemented the getMultiObjectClassInfo() method properly. It returns null which is not valid."); - initOK = false; - } -*/ - if (getConsumerEnvironment().getEventsEnabled() || getConsumerEnvironment().getDelayedEnabled()) - { - logger.debug("Events and/or Delayed Responses enabled => start local consumer queue for "+getConsumerName()); - createLocalConsumerQueue(); - } - else - { - logger.debug("Events AND Delayed Responses are disabled. Local consumer queues and threads are not started."); - } +// // Set some properties at this stage for simplicity reasons. +// checkACL = getConsumerEnvironment().getCheckACL(); +// +// if (getConsumerEnvironment().getEventsEnabled() || getConsumerEnvironment().getDelayedEnabled()) +// { +// logger.debug("Events and/or Delayed Responses enabled => start local consumer queue for "+getConsumerName()); +// createLocalConsumerQueue(); +// } +// else +// { +// logger.debug("Events AND Delayed Responses are disabled. Local consumer queues and threads are not started."); +// } } /** @@ -228,168 +183,168 @@ public AbstractConsumer() * * @return See desc */ - public ConsumerEnvironment getConsumerEnvironment() - { - return (ConsumerEnvironment)ConsumerEnvironmentManager.getInstance().getEnvironmentInfo(); - } +// public ConsumerEnvironment getConsumerEnvironment() +// { +// return (ConsumerEnvironment)ConsumerEnvironmentManager.getInstance().getEnvironmentInfo(); +// } - /** - * Utility method to easily retrieve the property file content for a consumer. - * - * @return See desc - */ - public AdvancedProperties getServiceProperties() - { - return ConsumerEnvironmentManager.getInstance().getServiceProperties(); - } +// /** +// * Utility method to easily retrieve the property file content for a consumer. +// * +// * @return See desc +// */ +// public AdvancedProperties getServiceProperties() +// { +// return ConsumerEnvironmentManager.getInstance().getServiceProperties(); +// } - /*------------------------------------------------------------------------------------------------------------------------ - * Start of 'Dynamic' HTTP Header Field override section - * - * The following set of methods are used for a more configurable way how some HTTP header parameters are set. - * By default the following HTTP Header fields are retrieved from the consumer's property file and put in corresponding - * HTTP Header Fields: - * - * Property HTTP Header - * ------------------------------------------------ - * adapter.generator.id generatorId - * env.application.key applicationKey - * env.userToken authenticatedUser - * env.mediaType Content-Type, Accept - * adapter.mustUseAdvisoryIDs mustUseAdvisory - * adapter.compression.enabled Content-Encoding, Accept-Encoding - * - * Only properties that are not null or empty string will be set in the corresponding HTTP Header. - * - * There are situations where and application may need a more 'dynamic' behaviour where the above values are determined - * at runtime, based on other circumstances and therefore these properties must be retrieved from an other source than the - * consumer's property file. In such a case the methods below can be overwritten to make them dynamic and controlled by - * the implementation rather than driven by the consumer's property file. If any of the methods below is overwritten then - * the value of the over riding method is set in the corresponding HTTP Header field if the return value of the method - * is not null or an empty string. - *------------------------------------------------------------------------------------------------------------------------*/ +// /*------------------------------------------------------------------------------------------------------------------------ +// * Start of 'Dynamic' HTTP Header Field override section +// * +// * The following set of methods are used for a more configurable way how some HTTP header parameters are set. +// * By default the following HTTP Header fields are retrieved from the consumer's property file and put in corresponding +// * HTTP Header Fields: +// * +// * Property HTTP Header +// * ------------------------------------------------ +// * adapter.generator.id generatorId +// * env.application.key applicationKey +// * env.userToken authenticatedUser +// * env.mediaType Content-Type, Accept +// * adapter.mustUseAdvisoryIDs mustUseAdvisory +// * adapter.compression.enabled Content-Encoding, Accept-Encoding +// * +// * Only properties that are not null or empty string will be set in the corresponding HTTP Header. +// * +// * There are situations where and application may need a more 'dynamic' behaviour where the above values are determined +// * at runtime, based on other circumstances and therefore these properties must be retrieved from an other source than the +// * consumer's property file. In such a case the methods below can be overwritten to make them dynamic and controlled by +// * the implementation rather than driven by the consumer's property file. If any of the methods below is overwritten then +// * the value of the over riding method is set in the corresponding HTTP Header field if the return value of the method +// * is not null or an empty string. +// *------------------------------------------------------------------------------------------------------------------------*/ +// +// /** +// * This method returns the value of the adapter.generator.id property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The adapter.generator.id property from the consumer's property file +// */ +// public String getGeneratorID() +// { +// return getConsumerEnvironment().getGeneratorID(); +// } +// +// /** +// * This method returns the value of the env.application.key property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The env.application.key property from the consumer's property file +// */ +// public String getApplicationKey() +// { +// return getConsumerEnvironment().getEnvironmentKey().getApplicationKey(); +// } +// +// /** +// * This method returns the value of the env.userToken property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The env.userToken property from the consumer's property file +// */ +// public String getAuthentictedUser() +// { +// return getConsumerEnvironment().getEnvironmentKey().getUserToken(); +// } +// +// /** +// * This method returns the value of the env.mediaType property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The env.mediaType property from the consumer's property file +// */ +// public MediaType getRequestMediaType() +// { +// return getConsumerEnvironment().getMediaType(); +// } +// +// /** +// * This method returns the value of the env.mediaType property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The env.mediaType property from the consumer's property file +// */ +// public MediaType getResponseMediaType() +// { +// return getConsumerEnvironment().getMediaType(); +// } +// +// /** +// * This method returns the value of the adapter.mustUseAdvisoryIDs property from the consumer's property file. If that +// * needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The adapter.mustUseAdvisoryIDs property from the consumer's property file +// */ +// public boolean getMustUseAdvisory() +// { +// return getConsumerEnvironment().getUseAdvisory(); +// } +// +// /** +// * This method returns the value of the adapter.compression.enabled property from the consumer's property file. If +// * that needs to be overridden by a specific implementation then the specific sub-class should override this method. +// * +// * @return The adapter.compression.enabled property from the consumer's property file +// */ +// public boolean getCompressionEnabled() +// { +// return getConsumerEnvironment().getCompressionEnabled(); +// } +// +// /*------------------------------------------------------------------------------------------------------------------------ +// * End of 'Dynamic' HTTP Header Field override section +// *-----------------------------------------------------------------------------------------------------------------------*/ + +// /** +// * @return Returns the actual Class Name of this consumer +// */ +// public String getConsumerName() +// { +// return getClass().getSimpleName(); +// } - /** - * This method returns the value of the adapter.generator.id property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The adapter.generator.id property from the consumer's property file - */ - public String getGeneratorID() - { - return getConsumerEnvironment().getGeneratorID(); - } +// /** +// * @return Returns the Service Name. +// */ +// public String getServiceName() +// { +// return getMultiObjectClassInfo().getObjectName(); +// } - /** - * This method returns the value of the env.application.key property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.application.key property from the consumer's property file - */ - public String getApplicationKey() - { - return getConsumerEnvironment().getEnvironmentKey().getApplicationKey(); - } - - /** - * This method returns the value of the env.userToken property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.userToken property from the consumer's property file - */ - public String getAuthentictedUser() - { - return getConsumerEnvironment().getEnvironmentKey().getUserToken(); - } - - /** - * This method returns the value of the env.mediaType property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.mediaType property from the consumer's property file - */ - public MediaType getRequestMediaType() - { - return getConsumerEnvironment().getMediaType(); - } - - /** - * This method returns the value of the env.mediaType property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.mediaType property from the consumer's property file - */ - public MediaType getResponseMediaType() - { - return getConsumerEnvironment().getMediaType(); - } - - /** - * This method returns the value of the adapter.mustUseAdvisoryIDs property from the consumer's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The adapter.mustUseAdvisoryIDs property from the consumer's property file - */ - public boolean getMustUseAdvisory() - { - return getConsumerEnvironment().getUseAdvisory(); - } - - /** - * This method returns the value of the adapter.compression.enabled property from the consumer's property file. If - * that needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The adapter.compression.enabled property from the consumer's property file - */ - public boolean getCompressionEnabled() - { - return getConsumerEnvironment().getCompressionEnabled(); - } - - /*------------------------------------------------------------------------------------------------------------------------ - * End of 'Dynamic' HTTP Header Field override section - *-----------------------------------------------------------------------------------------------------------------------*/ - - /** - * @return Returns the actual Class Name of this consumer - */ - public String getConsumerName() - { - return getClass().getSimpleName(); - } - - /** - * @return Returns the Service Name. - */ - public String getServiceName() - { - return getMultiObjectClassInfo().getObjectName(); - } - - /** - * Utility method. Mainly used for useful logging messages. - * - * @return Returns the Adapter Name as defined in the adapter.id property of the consumer property file concatenated with the - * Consumer Name (class name) - */ - public String getPrettyName() - { - return getConsumerEnvironment().getAdapterName()+" - " + getConsumerName(); - } - - /*------------------------------*/ - /* Some Getter & Setter methods */ - /*------------------------------*/ - - public final LocalConsumerQueue getLocalConsumerQueue() - { - return localConsumerQueue; - } - - public final void setLocalConsumerQueue(LocalConsumerQueue localConsumerQueue) - { - this.localConsumerQueue = localConsumerQueue; - } +// /** +// * Utility method. Mainly used for useful logging messages. +// * +// * @return Returns the Adapter Name as defined in the adapter.id property of the consumer property file concatenated with the +// * Consumer Name (class name) +// */ +// public String getPrettyName() +// { +// return getConsumerEnvironment().getAdapterName()+" - " + getConsumerName(); +// } + +// /*------------------------------*/ +// /* Some Getter & Setter methods */ +// /*------------------------------*/ +// +// public final LocalConsumerQueue getLocalConsumerQueue() +// { +// return localConsumerQueue; +// } +// +// public final void setLocalConsumerQueue(LocalConsumerQueue localConsumerQueue) +// { +// this.localConsumerQueue = localConsumerQueue; +// } /*-----------------------*/ /*-- Create Operations --*/ @@ -695,7 +650,7 @@ public List retrievByPrimaryKey(String resourceID, List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException + public List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, ServiceInvokationException { nullMethodCheck(getMultiObjectClassInfo(), "getMultiObjectClassInfo()"); /* @@ -716,14 +671,11 @@ public List retrieve(PagingInfo pagingInfo, List zone return responses; } - // Ensure query Intention is not null. if so default to ONE-OFF as per SIF 3.x spec. - queryIntention = (queryIntention == null) ? QueryIntention.ONE_OFF : queryIntention; - // Set default set of HTTP Header fields HeaderProperties hdrProps = getHeaderProperties(false, customParameters); - // Add query intention to headers. - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_QUERY_INTENTION, queryIntention.getHTTPHeaderValue()); + // Add query intention to headers. + addQueryIntentionToHeaders(hdrProps, queryIntention); List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); @@ -764,7 +716,7 @@ public List retrieve(PagingInfo pagingInfo, List zone * (non-Javadoc) * @see sif3.common.interfaces.QueryConsumer#retrieveByServicePath(sif3.common.model.QueryCriteria, sif3.common.model.PagingInfo, java.util.List, sif3.common.header.HeaderValues.RequestType, sif3.common.header.HeaderValues.QueryIntention, sif3.common.model.CustomParameters) */ - public List retrieveByServicePath(QueryCriteria queryCriteria, PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException + public List retrieveByServicePath(QueryCriteria queryCriteria, PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, ServiceInvokationException { nullMethodCheck(getMultiObjectClassInfo(), "getMultiObjectClassInfo()"); /* @@ -802,7 +754,7 @@ public List retrieveByServicePath(QueryCriteria queryCriteria, PagingI // Request operation in all zone/contexts as listed. for (ZoneContextInfo zoneCtx : finalZoneContextList) { - ErrorDetails error = allClientChecks(serviceName, AccessRight.QUERY, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + ErrorDetails error = allClientChecks(serviceName, null, AccessRight.QUERY, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), requestType); if (error == null) //all good => Send request { @@ -842,7 +794,7 @@ public List retrieveByQBE(Object exampleObject, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, - CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException + CustomParameters customParameters) throws PersistenceException, ServiceInvokationException { nullMethodCheck(getMultiObjectClassInfo(), "getMultiObjectClassInfo()"); /* @@ -1030,18 +982,17 @@ public List updateSingle(Object data, String resourceID, List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException + public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) throws PersistenceException, ServiceInvokationException { nullMethodCheck(getMultiObjectClassInfo(), "getMultiObjectClassInfo()"); /* @@ -1169,45 +1120,45 @@ public void onError(ErrorDetails error, DelayedResponseReceipt receipt) * * @return See desc. */ - public final LocalConsumerQueue createLocalConsumerQueue() - { - if (getLocalConsumerQueue() == null) - { - // Create local queue with the capacity indicated with the consumer config - logger.debug("Create Local Queue for "+getConsumerName()); - - // Use the local queue as a trigger of threads rather than actual queueing of messages. Use 1 as the minimum - setLocalConsumerQueue(new LocalConsumerQueue(1, getClass().getSimpleName() + "LocalQueue", getClass().getSimpleName())); - startListenerThreads(); - } - return getLocalConsumerQueue(); - } +// public final LocalConsumerQueue createLocalConsumerQueue() +// { +// if (getLocalConsumerQueue() == null) +// { +// // Create local queue with the capacity indicated with the consumer config +// logger.debug("Create Local Queue for "+getConsumerName()); +// +// // Use the local queue as a trigger of threads rather than actual queueing of messages. Use 1 as the minimum +// setLocalConsumerQueue(new LocalConsumerQueue(1, getClass().getSimpleName() + "LocalQueue", getClass().getSimpleName())); +// startListenerThreads(); +// } +// return getLocalConsumerQueue(); +// } /* * Will initialise the threads and add them to the local consumer queue. */ - private void startListenerThreads() - { - // Start up all consumers for this subscriber. - int numThreads = getNumOfConsumerThreads(); - logger.debug("Start "+numThreads+" "+getConsumerName()+" threads."); - logger.debug("Total number of threads before starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); - service = Executors.newFixedThreadPool(numThreads); - for (int i = 0; i < numThreads; i++) - { - String consumerID = getConsumerName()+" "+(i+1); - logger.debug("Start Consumer "+consumerID); - LocalMessageConsumer consumer = new LocalMessageConsumer(getLocalConsumerQueue(), consumerID, this); - service.execute(consumer); - } - logger.debug(numThreads+" "+getConsumerName()+" initilaised and started."); - logger.debug("Total number of threads after starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); - } - - private final int getNumOfConsumerThreads() - { - return getServiceProperties().getPropertyAsInt("consumer.local.workerThread", getClass().getSimpleName(), 1); - } +// private void startListenerThreads() +// { +// // Start up all consumers for this subscriber. +// int numThreads = getNumOfConsumerThreads(); +// logger.debug("Start "+numThreads+" "+getConsumerName()+" threads."); +// logger.debug("Total number of threads before starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); +// service = Executors.newFixedThreadPool(numThreads); +// for (int i = 0; i < numThreads; i++) +// { +// String consumerID = getConsumerName()+" "+(i+1); +// logger.debug("Start Consumer "+consumerID); +// LocalMessageConsumer consumer = new LocalMessageConsumer(getLocalConsumerQueue(), consumerID, this); +// service.execute(consumer); +// } +// logger.debug(numThreads+" "+getConsumerName()+" initilaised and started."); +// logger.debug("Total number of threads after starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); +// } + +// private final int getNumOfConsumerThreads() +// { +// return getServiceProperties().getPropertyAsInt("consumer.local.workerThread", getClass().getSimpleName(), 1); +// } /*----------------------------*/ /*-- Other required methods --*/ @@ -1237,24 +1188,32 @@ public List filterApprovedCRUDServices(List allService */ protected final List getAllApprovedCRUDServices() { - SIF3Session sif3Session = ConsumerEnvironmentManager.getInstance().getSIF3Session(); - List allServices = new ArrayList(); - - // Get OBJECT Services - List services = sif3Session.getServiceInfoForService(getMultiObjectClassInfo().getObjectName(), ServiceType.OBJECT); - for (ServiceInfo serviceInfo : services) - { - if (serviceInfo.getRights().hasRight(AccessRight.CREATE, AccessType.APPROVED) || - serviceInfo.getRights().hasRight(AccessRight.UPDATE, AccessType.APPROVED) || - serviceInfo.getRights().hasRight(AccessRight.DELETE, AccessType.APPROVED) || - serviceInfo.getRights().hasRight(AccessRight.QUERY, AccessType.APPROVED) ) - { - allServices.add(serviceInfo); - } - } - - // Now get SERVICEPATHs. They are only valid for QUERY permissions. No events or other types. - allServices.addAll(sif3Session.getServiceInfoForService(getMultiObjectClassInfo().getObjectName(), ServiceType.SERVICEPATH, AccessRight.QUERY, AccessType.APPROVED)); + List allServices = new ArrayList(); + + // Get all Object Services + allServices.addAll(getAllApprovedServicesForRights(getMultiObjectClassInfo().getObjectName(), ServiceType.OBJECT, AccessRight.CREATE, AccessRight.UPDATE, AccessRight.DELETE, AccessRight.QUERY)); + + // Get all ServicePath Services + allServices.addAll(getAllApprovedServicesForRights(getMultiObjectClassInfo().getObjectName(), ServiceType.SERVICEPATH, AccessRight.QUERY)); + +// SIF3Session sif3Session = ConsumerEnvironmentManager.getInstance().getSIF3Session(); +// List allServices = new ArrayList(); +// +// // Get OBJECT Services +// List services = sif3Session.getServiceInfoForService(getMultiObjectClassInfo().getObjectName(), ServiceType.OBJECT); +// for (ServiceInfo serviceInfo : services) +// { +// if (serviceInfo.getRights().hasRight(AccessRight.CREATE, AccessType.APPROVED) || +// serviceInfo.getRights().hasRight(AccessRight.UPDATE, AccessType.APPROVED) || +// serviceInfo.getRights().hasRight(AccessRight.DELETE, AccessType.APPROVED) || +// serviceInfo.getRights().hasRight(AccessRight.QUERY, AccessType.APPROVED) ) +// { +// allServices.add(serviceInfo); +// } +// } +// +// // Now get SERVICEPATHs. They are only valid for QUERY permissions. No events or other types. +// allServices.addAll(sif3Session.getServiceInfoForService(getMultiObjectClassInfo().getObjectName(), ServiceType.SERVICEPATH, AccessRight.QUERY, AccessType.APPROVED)); return filterApprovedCRUDServices(allServices); } @@ -1286,53 +1245,53 @@ private ObjectServiceClient getClient(ConsumerEnvironment envInfo) throws Illega } } - private SIF3Session getSIF3Session() - { - return ConsumerEnvironmentManager.getInstance().getSIF3Session(); - } - - private HeaderProperties getHeaderProperties(boolean isCreateOperation, HeaderValues.ServiceType serviceType, CustomParameters customParameters) - { - HeaderProperties hdrProps = new HeaderProperties(); - - // First we add all Custom HTTP Headers. We add SIF defined HTTP header later. This will also ensure that we - // will override custom properties with SIF defined properties. - if ((customParameters != null) && (customParameters.getHttpHeaderParams() != null)) - { - hdrProps = customParameters.getHttpHeaderParams(); - } - - // Now we set SIF defined HTTP headers... - - // Set the remaining header fields for this type of request - if (isCreateOperation) - { - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, (getMustUseAdvisory() ? "true" : "false")); - } - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, serviceType.name()); - - // Set values of consumer property file or their overridden value. Note thsetHeaderProperty() method will do the check - // for null, so no need to do this here. - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_APPLICATION_KEY, getApplicationKey()); - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTHENTICATED_USER, getAuthentictedUser()); - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_GENERATOR_ID, getGeneratorID()); - - return hdrProps; - } +// private SIF3Session getSIF3Session() +// { +// return ConsumerEnvironmentManager.getInstance().getSIF3Session(); +// } + +// private HeaderProperties getHeaderProperties(boolean isCreateOperation, HeaderValues.ServiceType serviceType, CustomParameters customParameters) +// { +// HeaderProperties hdrProps = new HeaderProperties(); +// +// // First we add all Custom HTTP Headers. We add SIF defined HTTP header later. This will also ensure that we +// // will override custom properties with SIF defined properties. +// if ((customParameters != null) && (customParameters.getHttpHeaderParams() != null)) +// { +// hdrProps = customParameters.getHttpHeaderParams(); +// } +// +// // Now we set SIF defined HTTP headers... +// +// // Set the remaining header fields for this type of request +// if (isCreateOperation) +// { +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, (getMustUseAdvisory() ? "true" : "false")); +// } +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, serviceType.name()); +// +// // Set values of consumer property file or their overridden value. Note thsetHeaderProperty() method will do the check +// // for null, so no need to do this here. +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_APPLICATION_KEY, getApplicationKey()); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTHENTICATED_USER, getAuthentictedUser()); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_GENERATOR_ID, getGeneratorID()); +// +// return hdrProps; +// } private HeaderProperties getHeaderProperties(boolean isCreateOperation, CustomParameters customParameters) { - return getHeaderProperties(isCreateOperation, HeaderValues.ServiceType.OBJECT, customParameters); + return getHeaderProperties(isCreateOperation, ServiceType.OBJECT, customParameters); } - private void setErrorDetails(BaseResponse response, ErrorDetails errorDetails) - { - response.setStatus(errorDetails.getErrorCode()); - response.setStatusMessage(errorDetails.getMessage()); - response.setError(errorDetails); - response.setContentLength(0); - response.setHasEntity(false); - } +// private void setErrorDetails(BaseResponse response, ErrorDetails errorDetails) +// { +// response.setStatus(errorDetails.getErrorCode()); +// response.setStatusMessage(errorDetails.getMessage()); +// response.setError(errorDetails); +// response.setContentLength(0); +// response.setHasEntity(false); +// } /* * Will perform hasAccess() and requestTypeSupported() checks. This is a convenience method, so that not each operation has to @@ -1340,65 +1299,65 @@ private void setErrorDetails(BaseResponse response, ErrorDetails errorDetails) */ private ErrorDetails allClientChecks(AccessRight right, AccessType accessType, SIFZone zone, SIFContext context, RequestType requestType) { - return allClientChecks(getMultiObjectClassInfo().getObjectName(), right, accessType, zone, context, requestType); - } - - private ErrorDetails allClientChecks(String serviceName, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context, RequestType requestType) - { - ErrorDetails error = hasAccess(serviceName, right, accessType, zone, context); - if ((error == null) && (requestType != null)) - { - error = requestTypeEnabled(requestType); - } - return error; - } - - private ErrorDetails hasAccess(String serviceName, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context) - { - ErrorDetails error = null; - if (checkACL) - { - if (!getSIF3Session().hasAccess(right, accessType, serviceName, zone, context)) - { - String zoneID = (zone == null) ? "Default" : zone.getId(); - String contextID = (context == null) ? "Default" : context.getId(); - error = new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Consumer is not authorized to issue the requested operation.", right.name()+ " access is not set to "+accessType.name()+" for the service " + serviceName +" and the given zone ("+zoneID+") and context ("+contextID+") in the environment "+getSIF3Session().getEnvironmentName(), "Client side check."); - } - } - return error; + return allClientChecks(getMultiObjectClassInfo().getObjectName(), null, right, accessType, zone, context, requestType); } - private ErrorDetails requestTypeEnabled(RequestType requestType) - { - ErrorDetails error = null; +// private ErrorDetails allClientChecks(String serviceName, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context, RequestType requestType) +// { +// ErrorDetails error = hasAccess(serviceName, right, accessType, zone, context); +// if ((error == null) && (requestType != null)) +// { +// error = requestTypeEnabled(requestType); +// } +// return error; +// } - if ((requestType == RequestType.DELAYED) && (!getConsumerEnvironment().getDelayedEnabled())) - { - error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Client side Check: DELAYED requests are not enabled."); - } - return error; - } - - private Response createErrorResponse(ErrorDetails error) - { - Response response = new Response(); - setErrorDetails(response, error); - return response; - } +// private ErrorDetails hasAccess(String serviceName, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context) +// { +// ErrorDetails error = null; +// if (checkACL) +// { +// if (!getSIF3Session().hasAccess(right, accessType, serviceName, null, zone, context)) +// { +// String zoneID = (zone == null) ? "Default" : zone.getId(); +// String contextID = (context == null) ? "Default" : context.getId(); +// error = new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Consumer is not authorized to issue the requested operation.", right.name()+ " access is not set to "+accessType.name()+" for the service " + serviceName +" and the given zone ("+zoneID+") and context ("+contextID+") in the environment "+getSIF3Session().getEnvironmentName(), "Client side check."); +// } +// } +// return error; +// } - private BulkOperationResponse makeBulkErrorResponseForCreates(ErrorDetails error) - { - BulkOperationResponse response = new BulkOperationResponse(); - setErrorDetails(response, error); - return response; - } +// private ErrorDetails requestTypeEnabled(RequestType requestType) +// { +// ErrorDetails error = null; +// +// if ((requestType == RequestType.DELAYED) && (!getConsumerEnvironment().getDelayedEnabled())) +// { +// error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Client side Check: DELAYED requests are not enabled."); +// } +// return error; +// } + +// private Response createErrorResponse(ErrorDetails error) +// { +// Response response = new Response(); +// setErrorDetails(response, error); +// return response; +// } - private BulkOperationResponse makeBulkErrorResponse(ErrorDetails error) - { - BulkOperationResponse response = new BulkOperationResponse(); - setErrorDetails(response, error); - return response; - } +// private BulkOperationResponse makeBulkErrorResponseForCreates(ErrorDetails error) +// { +// BulkOperationResponse response = new BulkOperationResponse(); +// setErrorDetails(response, error); +// return response; +// } +// +// private BulkOperationResponse makeBulkErrorResponse(ErrorDetails error) +// { +// BulkOperationResponse response = new BulkOperationResponse(); +// setErrorDetails(response, error); +// return response; +// } private String getServiceName(QueryCriteria queryCriteria) { @@ -1430,85 +1389,80 @@ private String getServicePath(QueryCriteria queryCriteria) return result; } -// @SuppressWarnings("unused") - private List getFinalZoneCtxList( List zoneCtxList, SIF3Session sif3Session) - { - List finalZoneContextList = null; - - if (zoneCtxList == null) - { - finalZoneContextList = new ArrayList(); - } - else - { - finalZoneContextList = zoneCtxList; - } - - if (finalZoneContextList.size() == 0) //add default context and zone - { - // Set zone and context to null which will ensure that the matrix params won't be set and therefore the provider will assume default context & zone - finalZoneContextList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); - -// finalZoneContextList.add(new ZoneContextInfo(new SIFZone(sif3Session.getDefaultZone().getId(), true), new SIFContext(CommonConstants.DEFAULT_CONTEXT_NAME, true))); - } - - // Check all entries and if 'null' is used as zone or context then we assign the default. - for (ZoneContextInfo zoneCtxInfo : finalZoneContextList) - { - // If zone or zone ID is null then we set the default zone. - if ((zoneCtxInfo.getZone() == null) || StringUtils.isEmpty(zoneCtxInfo.getZone().getId())) - { - zoneCtxInfo.setZone(null); // won't set matrix parameter which means default zone -// zoneCtxInfo.setZone(new SIFZone(sif3Session.getDefaultZone().getId(), true)); - } - // If context or context ID is null then we set the default zone. - if ((zoneCtxInfo.getContext() == null) || StringUtils.isEmpty(zoneCtxInfo.getContext().getId())) - { - zoneCtxInfo.setContext(null); // won't set matrix parameter which means default context -// zoneCtxInfo.setContext(new SIFContext(CommonConstants.DEFAULT_CONTEXT_NAME, true)); - } - } - - return finalZoneContextList; - } +// private List getFinalZoneCtxList( List zoneCtxList, SIF3Session sif3Session) +// { +// List finalZoneContextList = null; +// +// if (zoneCtxList == null) +// { +// finalZoneContextList = new ArrayList(); +// } +// else +// { +// finalZoneContextList = zoneCtxList; +// } +// +// if (finalZoneContextList.size() == 0) //add default context and zone +// { +// // Set zone and context to null which will ensure that the matrix params won't be set and therefore the provider will assume default context & zone +// finalZoneContextList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); +// } +// +// // Check all entries and if 'null' is used as zone or context then we assign the default. +// for (ZoneContextInfo zoneCtxInfo : finalZoneContextList) +// { +// // If zone or zone ID is null then we set the default zone. +// if ((zoneCtxInfo.getZone() == null) || StringUtils.isEmpty(zoneCtxInfo.getZone().getId())) +// { +// zoneCtxInfo.setZone(null); // won't set matrix parameter which means default zone +// } +// // If context or context ID is null then we set the default zone. +// if ((zoneCtxInfo.getContext() == null) || StringUtils.isEmpty(zoneCtxInfo.getContext().getId())) +// { +// zoneCtxInfo.setContext(null); // won't set matrix parameter which means default context +// } +// } +// +// return finalZoneContextList; +// } /* * This method sets the remaining properties in the receipt for delayed responses. There are a few fields that cannot be set at the ObjectServiceClient as * they are not known or cannot be determined in there but are well known in the abstract consumer. */ - private void finaliseDelayedReceipt(DelayedRequestReceipt delayedReceipt, String serviceName, ServiceType serviceType, ResponseAction requestedAction) - { - if (delayedReceipt != null) - { - //delayedReceipt.setRequestDate(requestDate); - delayedReceipt.setServiceName(serviceName); - delayedReceipt.setServiceType(serviceType); - delayedReceipt.setRequestedAction(requestedAction); - } - } +// private void finaliseDelayedReceipt(DelayedRequestReceipt delayedReceipt, String serviceName, ServiceType serviceType, ResponseAction requestedAction) +// { +// if (delayedReceipt != null) +// { +// //delayedReceipt.setRequestDate(requestDate); +// delayedReceipt.setServiceName(serviceName); +// delayedReceipt.setServiceType(serviceType); +// delayedReceipt.setRequestedAction(requestedAction); +// } +// } - private void nullMethodCheck(Object objectToCheck, String methodName) throws IllegalArgumentException - { - if (objectToCheck == null) - { - throw new IllegalArgumentException(methodName+" method not implemented correctly. Returns null which is invalid."); - } - } +// private void nullMethodCheck(Object objectToCheck, String methodName) throws IllegalArgumentException +// { +// if (objectToCheck == null) +// { +// throw new IllegalArgumentException(methodName+" method not implemented correctly. Returns null which is invalid."); +// } +// } /* * This method checks if the test.testmode in the consumer's property file is set to TRUE. */ - @SuppressWarnings("unused") - private boolean isTestMode() - { - if (testMode == null) - { - AdvancedProperties props = getServiceProperties(); - testMode = props.getPropertyAsBool("test.testmode", false); - } - return testMode; - } +// @SuppressWarnings("unused") +// private boolean isTestMode() +// { +// if (testMode == null) +// { +// AdvancedProperties props = getServiceProperties(); +// testMode = props.getPropertyAsBool("test.testmode", false); +// } +// return testMode; +// } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractEventConsumer.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractEventConsumer.java index 4c3daab2..c516c77e 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractEventConsumer.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractEventConsumer.java @@ -22,13 +22,10 @@ import sif3.common.header.HeaderValues.ServiceType; import sif3.common.interfaces.EventConsumer; +import sif3.common.model.ACL.AccessRight; import sif3.common.model.EventMetadata; import sif3.common.model.SIFEvent; import sif3.common.model.ServiceInfo; -import sif3.common.model.ServiceRights; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.persist.model.SIF3Session; -import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; /** * This is the core class that a developer will use to implement for a consumer that shall subscribe to events. Each consumer for each object @@ -69,11 +66,8 @@ public AbstractEventConsumer() * @see sif3.common.interfaces.EventConsumer#onEvent(sif3.common.model.SIFEvent, sif3.common.model.EventMetadata, java.lang.String, java.lang.String) */ @Override -// public void onEvent(SIFEvent sifEvent, SIFZone zone, SIFContext context, EventMetadata metadata, String msgReadID, String consumerID) public void onEvent(SIFEvent sifEvent, EventMetadata metadata, String msgReadID, String consumerID) { - // Right now all that is required is to call the abstract processEvent() method. -// processEvent(sifEvent, zone, context, metadata, msgReadID, consumerID); processEvent(sifEvent, metadata, msgReadID, consumerID); } @@ -129,11 +123,12 @@ public List filterEventServices(List envEventServices) */ protected final List getEventServices() { - SIF3Session sif3Session = ConsumerEnvironmentManager.getInstance().getSIF3Session(); - return filterEventServices(sif3Session.getServiceInfoForService(getMultiObjectClassInfo().getObjectName(), ServiceType.OBJECT, AccessRight.SUBSCRIBE, ServiceRights.AccessType.APPROVED)); + List eventServices = getAllApprovedServicesForRights(getMultiObjectClassInfo().getObjectName(), ServiceType.OBJECT, AccessRight.SUBSCRIBE); + + return filterEventServices(eventServices); } /*---------------------*/ /*-- Private Methods --*/ /*---------------------*/ - } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractFunctionalServiceConsumer.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractFunctionalServiceConsumer.java new file mode 100644 index 00000000..9117605b --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/AbstractFunctionalServiceConsumer.java @@ -0,0 +1,1265 @@ +/* + * AbstractFunctionalServiceConsumer.java + * Created: 13 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.consumer; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.StringUtils; +import au.com.systemic.framework.utils.Timer; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.conversion.MarshalFactory; +import sif3.common.conversion.ModelObjectInfo; +import sif3.common.conversion.UnmarshalFactory; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.ServiceInvokationException; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.header.HeaderValues.QueryIntention; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.header.HeaderValues.UpdateType; +import sif3.common.interfaces.DelayedConsumer; +import sif3.common.interfaces.EventConsumer; +import sif3.common.interfaces.FunctionalServiceConsumer; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; +import sif3.common.model.CustomParameters; +import sif3.common.model.EventMetadata; +import sif3.common.model.PagingInfo; +import sif3.common.model.QueryCriteria; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFEvent; +import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; +import sif3.common.model.URLQueryParameter; +import sif3.common.model.ZoneContextInfo; +import sif3.common.model.delayed.DelayedResponseReceipt; +import sif3.common.model.job.JobCreateRequestParameter; +import sif3.common.model.job.PhaseInfo; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.ErrorDetails; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.common.ws.job.PhaseDataResponse; +import sif3.common.ws.model.MultiOperationStatusList; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; +import sif3.infra.common.env.types.ConsumerEnvironment; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; +import sif3.infra.rest.client.JobClient; + +/** + * This is the core class that a developer will use to implement their functional service consumers. Each consumer for each functional + * service MUST extend this class. It forms the link between the high level consumer implementation and the low level infrastructure + * functions which this class abstracts.
+ * This class has some similarities to the Object service classes (AbstractConsumer and AbstarctEventConsumer). However it adds + * required functional service operations such a phase and state functions that are only applicable for functional service. Since + * functional services operate on the Job Object a number of assumptions can be made that aren't valid for the abstract consumer that + * works with SIF3 DM objects. This class makes use of the knowledge of the Job Object and therefore can implement a number of + * methods that are otherwise left to the developer (i.e. Eventing, Changes Since etc). + * Functional Services have a number of methods that are specific for them such as phase operations. These methods are stubbed out + * in this implementations similar to CRUD operations of the abstract consumer that works with Object Services.
+ * It is assumed that the ConsumerLoader.initialise() method has been called before any methods of this class are called.If not then + * the behaviour of this class is not defined. In fact each call to any method of this class will first test if initialisation has + * succeeded and no action in any of the top level methods will be executed if the ConsumerLoader.initialise() hasn't been called before. + * + * @author Joerg Huber + */ +public abstract class AbstractFunctionalServiceConsumer extends BaseConsumer implements FunctionalServiceConsumer, DelayedConsumer, EventConsumer, Runnable +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + public static final ModelObjectInfo JOBS_INFO = new ModelObjectInfo("jobs", JobCollectionType.class); + public static final ModelObjectInfo JOB_INFO = new ModelObjectInfo("job", JobType.class); + + private MarshalFactory marshaller = new InfraMarshalFactory(); + private UnmarshalFactory unmarshaller = new InfraUnmarshalFactory(); + + /*--------------------------------------------------------------------*/ + /* Abstract method relating to Functional Service Type functionality. */ + /*--------------------------------------------------------------------*/ + /** + * Must return the name of the Functional Service. This must match the value of an entry in the + * SIF3_JOB_TEMPLATE with the JOB_URL_NAME column. It is also the equivalent to the corresponding segment in + * in the Functional Service URL (eg. .../serviceConnector/jobs/).

+ * + * Example
+ * https://sif3hub.edu.au/services/jobs/endofyearrollovers + * + * @return A not null or empty value. The name of the functional service in its plural form. + */ + public abstract String getServiceURLNamePlural(); + + /** + * Must return the name of the Functional Service. This must match the value of an entry in the + * SIF3_JOB_TEMPLATE with the JOB_URL_NAME column. It is also the equivalent to the corresponding segment in + * in the Functional Service URL (eg. .../serviceConnector/jobs//).

+ * + * Example
+ * https://sif3hub.edu.au/services/jobs/endofyearrollovers/endofyearrollover (note there is no 's' in the singular form) + * + * @return A not null or empty value. The name of the functional service in its singular form. + */ + public abstract String getServiceURLNameSingular(); + + /*---------------------------------------------------------------------*/ + /* Abstract method relating to DELAYED request response functionality. */ + /*---------------------------------------------------------------------*/ + + /*----------------------------*/ + /*-- Delayed Job Operations --*/ + /*----------------------------*/ + + /** + * This method is called when a delayed create response for jobs is retrieved from the consumer's queue.

+ * + * @see sif3.common.interfaces.DelayedConsumer#onCreateMany(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + * + * @param statusList See onCreateMany() method in DelayedConsumer class. + * @param receipt See onCreateMany() method in DelayedConsumer class. + */ + public abstract void processDelayedJobsCreate(MultiOperationStatusList statusList, DelayedResponseReceipt receipt); + + /** + * This method is called when a delayed delete jobs response is retrieved from the consumer's queue.

+ * + * @see sif3.common.interfaces.DelayedConsumer#onDeleteMany(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + * + * @param statusList See onDeleteMany() method in DelayedConsumer class. + * @param receipt See onDeleteMany() method in DelayedConsumer class. + */ + public abstract void processDelayedJobsDelete(MultiOperationStatusList statusList, DelayedResponseReceipt receipt); + + /** + * This method is called when a delayed jobs query response is retrieved from the consumer's queue.

+ * + * @see sif3.common.interfaces.DelayedConsumer#onQuery(java.lang.Object, sif3.common.model.PagingInfo, sif3.common.model.delayed.DelayedResponseReceipt) + * + * @param jobs See onQuery() method in DelayedConsumer class. + * @param pagingInfo See onQuery() method in DelayedConsumer class. + * @param receipt See onQuery() method in DelayedConsumer class. + */ + public abstract void processDelayedJobsQuery(JobCollectionType jobs, PagingInfo pagingInfo, DelayedResponseReceipt receipt); + + /*------------------------------*/ + /*-- Delayed Phase Operations --*/ + /*------------------------------*/ + + /** + * This method is called when a delayed phase query response is retrieved from the consumer's queue.

+ * + * @param phaseInfo Holds the jobID and phase name for which the delayed response is for. + * @param phaseDataResponse Holds data that is returned. Data can be empty. The data is in its raw format, that being a string. Because + * the framework is data model agnostic it cannot make any assumptions what the data is. It is an entirely + * implementation specific piece of knowledge. Hence only the string is passed to the consumer. It is up to the + * consumer to unmarshal the string into a suitable structure. For the unmarshal operation the mime type in this + * parameter should be used. It indicates what the data's mime type is. + * @param pagingInfo The paging information relating to the query result that is returned. Because a consumer may request + * query results in pages it is necessary to pass that paging information back to the consumer as part + * of this call. This may allow the consumer to determine how many pages it may expect as well as if it + * has paged through all results. + * @param receipt Metadata information about the response relating to the original request. This includes but is not + * limited to the zoneId, contextId, original request ID etc. as well as all HTTP Headers of the response. + */ + public abstract void processDelayedPhaseQuery(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, PagingInfo pagingInfo, DelayedResponseReceipt receipt); + + /** + * This method is called when a delayed phase create response is retrieved from the consumer's queue.

+ * + * @param phaseInfo Holds the jobID and phase name for which the delayed response is for. + * @param phaseDataResponse Holds data that is returned. Data can be empty. The data is in its raw format, that being a string. Because + * the framework is data model agnostic it cannot make any assumptions what the data is. It is an entirely + * implementation specific piece of knowledge. Hence only the string is passed to the consumer. It is up to the + * consumer to unmarshal the string into a suitable structure. For the unmarshal operation the mime type in this + * parameter should be used. It indicates what the data's mime type is. + * @param receipt Metadata information about the response relating to the original request. This includes but is not + * limited to the zoneId, contextId, original request ID etc. as well as all HTTP Headers of the response. + */ + public abstract void processDelayedPhaseCreate(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt); + + /** + * This method is called when a delayed phase update response is retrieved from the consumer's queue.

+ * + * @param phaseInfo Holds the jobID and phase name for which the delayed response is for. + * @param phaseDataResponse Holds data that is returned. Data can be empty. The data is in its raw format, that being a string. Because + * the framework is data model agnostic it cannot make any assumptions what the data is. It is an entirely + * implementation specific piece of knowledge. Hence only the string is passed to the consumer. It is up to the + * consumer to unmarshal the string into a suitable structure. For the unmarshal operation the mime type in this + * parameter should be used. It indicates what the data's mime type is. + * @param receipt Metadata information about the response relating to the original request. This includes but is not + * limited to the zoneId, contextId, original request ID etc. as well as all HTTP Headers of the response. + */ + public abstract void processDelayedPhaseUpdate(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt); + + /** + * This method is called when a delayed phase delete response is retrieved from the consumer's queue.

+ * + * @param phaseInfo Holds the jobID and phase name for which the delayed response is for. + * @param phaseDataResponse Holds data that is returned. Data can be empty. The data is in its raw format, that being a string. Because + * the framework is data model agnostic it cannot make any assumptions what the data is. It is an entirely + * implementation specific piece of knowledge. Hence only the string is passed to the consumer. It is up to the + * consumer to unmarshal the string into a suitable structure. For the unmarshal operation the mime type in this + * parameter should be used. It indicates what the data's mime type is. + * @param receipt Metadata information about the response relating to the original request. This includes but is not + * limited to the zoneId, contextId, original request ID etc. as well as all HTTP Headers of the response. + */ + public abstract void processDelayedPhaseDelete(PhaseInfo phaseInfo, PhaseDataResponse phaseDataResponse, DelayedResponseReceipt receipt); + + /*------------------------------------------------------*/ + /* Abstract method relating to Job Event functionality. */ + /*------------------------------------------------------*/ + /** + * This method is called when a consumer service has received a Job event. This class does implement the actual onEvent() + * method from the event interface. It may do some additional work for house keeping purpose so the original onEvent() id processed + * as part of this class but then this method is called so that the actual consumer can do its work as required. + * + * @param sifEvent The event data that has been received and shall be processed by the consumer. This parameter also holds + * the zone and context in the limitToZoneCtxList property. It will always only hold one entry in that + * list. So the zone can be retrieved with the following call: sifEvent.getLimitToZoneCtxList().get(0).getZone(). + * However for simplicity reasons the zone and context is already extracted and passed to this method in the + * zone anc context parameter. + * @param zone The zone from which the event was received from. The framework ensures that this is never null. + * @param context The context from which the event was received from. The framework ensures that this is never null. + * @param metadata Additional metadata that is known for the event. Typical values include custom HTTP headers, sourceName etc. + * @param msgReadID The ID of the SIF queue reader. It is informative only and is only of use where there are multiple concurrent + * subscribers on a message queue. + * @param consumerID The consumer ID that has been used to receive the event from the event queue. It is informative + * only and is only of use where there are multiple event subscribers enabled. + */ + public abstract void processJobEvent(SIFEvent sifEvent, SIFZone zone, SIFContext context, EventMetadata metadata, String msgReadID, String consumerID); + + /*-------------------------*/ + /* Implementation of Class */ + /*-------------------------*/ + + /** + * Constructor. + */ + public AbstractFunctionalServiceConsumer() + { + super(); + } + + /*------------------------------------*/ + /* DataModelLink Interface Methods. --*/ + /*------------------------------------*/ + + /* + * This DataModelLink is configured for the Job Object, which is an infrastructure object. Because Functional Services are not + * Object Services and therefore we know what object we deal with. + */ + + /* (non-Javadoc) + * @see sif3.common.interfaces.DataModelLink#getMarshaller() + */ + @Override + public MarshalFactory getMarshaller() + { + return marshaller; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DataModelLink#getUnmarshaller() + */ + @Override + public UnmarshalFactory getUnmarshaller() + { + return unmarshaller; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DataModelLink#getSingleObjectClassInfo() + */ + @Override + public ModelObjectInfo getSingleObjectClassInfo() + { + return JOB_INFO; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DataModelLink#getMultiObjectClassInfo() + */ + @Override + public ModelObjectInfo getMultiObjectClassInfo() + { + return JOBS_INFO; + } + + /*------------------------------------*/ + /* MinimalConsumer Interface Methods. --*/ + /*------------------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.MinimalConsumer#retrievByPrimaryKey(java.lang.String, java.util.List, sif3.common.model.CustomParameters) + */ + @Override + public List retrievByPrimaryKey(String resourceID, List zoneCtxList, CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + nullJobNameMethodCheck(getServiceURLNameSingular(), "getServiceURLNameSingular()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List responses = new ArrayList(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.QUERY, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), null); + if (error == null) //all good + { + responses.add(getClient(getConsumerEnvironment()).getJob(resourceID, getHeaderProperties(false, customParameters), urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext())); + } + else //pretend to have received a 'fake' error Response + { + responses.add(createErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'retrieve by job by jobID key' for "+getServiceURLNamePlural()+"/"+resourceID+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but but uses JobId in method name which is more intuitive than the generic + * interface method.. + */ + public List retrievByJobID(String jobID, List zoneCtxList, CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return retrievByPrimaryKey(jobID, zoneCtxList, customParameters); + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List retrievByJobId(String resourceID, List zoneCtxList) throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return retrievByPrimaryKey(resourceID, zoneCtxList, null); + } + + + /* (non-Javadoc) + * @see sif3.common.interfaces.MinimalConsumer#retrieve(sif3.common.model.PagingInfo, java.util.List, sif3.common.header.HeaderValues.RequestType, sif3.common.header.HeaderValues.QueryIntention, sif3.common.model.CustomParameters) + */ + @Override + public List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) + throws PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List responses = new ArrayList(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + // Set default set of HTTP Header fields + HeaderProperties hdrProps = getHeaderProperties(false, customParameters); + + // Add query intention to headers. + addQueryIntentionToHeaders(hdrProps, queryIntention); + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.QUERY, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + if (error == null) //all good => Send request + { + Response response = getClient(getConsumerEnvironment()).getJobs(pagingInfo, hdrProps, urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.QUERY); + responses.add(response); + } + else //pretend to have received a 'fake' error Response + { + responses.add(createErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'retrieve all Jobs' for "+getServiceURLNamePlural()+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but but uses JobId in method name which is more intuitive than the generic + * interface method.. + */ + public List retrieveJobs(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) + throws PersistenceException, ServiceInvokationException + { + return retrieve(pagingInfo, zoneCtxList, requestType, queryIntention, customParameters); + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List retrieveJobs(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention) + throws PersistenceException, ServiceInvokationException + { + return retrieve(pagingInfo, zoneCtxList, requestType, queryIntention, null); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.MinimalConsumer#getServiceInfo(sif3.common.model.PagingInfo, java.util.List, sif3.common.model.CustomParameters) + */ + @Override + public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) + throws PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List responses = new ArrayList(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + // Set default set of HTTP Header fields + HeaderProperties hdrProps = getHeaderProperties(false, customParameters); + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.QUERY, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), RequestType.IMMEDIATE); + if (error == null) //all good => Send request + { + Response response = getClient(getConsumerEnvironment()).getServiceInfo(pagingInfo, hdrProps, urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext()); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.HEAD); + responses.add(response); + } + else //pretend to have received a 'fake' error Response + { + responses.add(createErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'retrieve service info' for "+getServiceURLNamePlural()+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList) throws PersistenceException, ServiceInvokationException + { + return getServiceInfo(pagingInfo, zoneCtxList, null); + } + + /* + * (non-Javadoc) + * @see sif3.common.consumer.MinimalConsumer#finalise() + */ + @Override + public void finalise() + { + // Clean up stuff for this consumer + + // Clean up the BaseConsumer + super.finalise(); + + // Clean up the specific consumer implementation + shutdown(); + } + + /*--------------------------------------*/ + /* DelayedConsumer Interface Methods. --*/ + /*--------------------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onCreateMany(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onCreateMany(MultiOperationStatusList statusList, DelayedResponseReceipt receipt) + { + processDelayedJobsCreate(statusList, receipt); + } + + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onDeleteMany(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onDeleteMany(MultiOperationStatusList statusList, DelayedResponseReceipt receipt) + { + processDelayedJobsDelete(statusList, receipt); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onQuery(java.lang.Object, sif3.common.model.PagingInfo, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onQuery(Object dataObject, PagingInfo pagingInfo, DelayedResponseReceipt receipt) + { + if (dataObject != null) + { + if (dataObject instanceof JobCollectionType) + { + processDelayedJobsQuery((JobCollectionType)dataObject, pagingInfo, receipt); + } + else + { + // We have received a job query response but the data is not of type JobCollectionType. This is not good. We log an error an + // also call the onError method to indicate to the consumer that some odd stuff has come through. + processDelayedError(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Invalid Data", "A delayed response to a Job Query has been received but the data is of type "+dataObject.getClass().getSimpleName()+" instead of JobCollectiontype.", "Consumer"), receipt); + } + } + else + { + processDelayedJobsQuery(null, pagingInfo, receipt); + } + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onError(sif3.common.ws.ErrorDetails, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onError(ErrorDetails error, DelayedResponseReceipt receipt) + { + processDelayedError(error, receipt); + } + + /* + * The following methods are not applicable for Job Objects. No implementation required. + */ + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onServicePath(java.lang.Object, sif3.common.model.QueryCriteria, sif3.common.model.PagingInfo, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onServicePath(Object dataObject, QueryCriteria queryCriteria, PagingInfo pagingInfo, DelayedResponseReceipt receipt) + { + //There is no ServicePath functionality for functional services or job object defined, so we do not need to implement anything here. + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.DelayedConsumer#onUpdateMany(sif3.common.ws.model.MultiOperationStatusList, sif3.common.model.delayed.DelayedResponseReceipt) + */ + @Override + public void onUpdateMany(MultiOperationStatusList statusList, DelayedResponseReceipt receipt) + { + //Job Objects cannot be updated. No implementation needed here. + } + + /*------------------------------------*/ + /* EventConsumer Interface Methods. --*/ + /*------------------------------------*/ + + @Override + public void onEvent(SIFEvent sifEvent, EventMetadata metadata, String msgReadID, String consumerID) + { + if (sifEvent != null) + { + // We know from the framework that zone and context is never null. For the time being we just log the event. + processJobEvent(sifEvent, sifEvent.getLimitToZoneCtxList().get(0).getZone(), sifEvent.getLimitToZoneCtxList().get(0).getContext(), metadata, msgReadID, consumerID); + } + } + + @Override + public SIFEvent createEventObject(Object sifObjectList, EventAction eventAction, UpdateType updateType) + { + if (sifObjectList != null) + { + if (sifObjectList instanceof JobCollectionType) + { + int size = ((JobCollectionType)sifObjectList).getJob().size(); + return new SIFEvent((JobCollectionType)sifObjectList, eventAction, updateType, size); + } + else + { + logger.error("The given event data is not of type JobCollectionType as expected. Cannot create event object. Return null"); + } + } + else + { + logger.error("The given job event data is null. Cannot create job event object. Return null"); + } + return null; // if something is wrong then we get here. + } + + /*------------------------------------------------*/ + /* FunctionalServiceConsumer Interface Methods. --*/ + /*------------------------------------------------*/ + + /*--------------------*/ + /*-- Job Operations --*/ + /*--------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#createJob(sif3.common.model.job.JobCreateRequestParameter, java.util.List, sif3.common.model.CustomParameters) + */ + @Override + public List createJob(JobCreateRequestParameter createJobRequest, List zoneCtxList, CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + nullJobNameMethodCheck(getServiceURLNameSingular(), "getServiceURLNameSingular()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List responses = new ArrayList(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.CREATE, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), null); + if (error == null) //all good + { + responses.add(getClient(getConsumerEnvironment()).createJob(createJobRequest, getHeaderProperties(true, customParameters), urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext())); + } + else //pretend to have received a 'fake' error Response + { + responses.add(createErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'createJob' for "+getServiceURLNamePlural()+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List createJob(JobCreateRequestParameter createJobRequest, List zoneCtxList) throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return createJob(createJobRequest, zoneCtxList, null); + } + + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#deleteJob(java.lang.String, java.util.List, sif3.common.model.CustomParameters) + */ + @Override + public List deleteJob(String jobID, List zoneCtxList, CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List responses = new ArrayList(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.DELETE, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), null); + if (error == null) //all good + { + responses.add(getClient(getConsumerEnvironment()).removeJob(jobID, getHeaderProperties(false, customParameters), urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext())); + } + else //pretend to have received a 'fake' error Response + { + responses.add(createErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'delete job by jobID' for "+getServiceURLNamePlural()+"/"+jobID+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List deleteJob(String jobID, List zoneCtxList) throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return deleteJob(jobID, zoneCtxList, null); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#createJobs(java.util.List, java.util.List, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public List> createJobs(List createMultipleJobsRequest, + List zoneCtxList, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List> responses = new ArrayList>(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.CREATE, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + if (error == null) //all good => Send request. + { + BulkOperationResponse response = getClient(getConsumerEnvironment()).createJobs(createMultipleJobsRequest, getHeaderProperties(true, customParameters), urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.CREATE); + responses.add(response); + } + else //pretend to have received a 'fake' error Response + { + responses.add(makeBulkErrorResponseForCreates(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'createJobs' for "+getServiceURLNamePlural()+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List> createJobs(List createMultipleJobsRequest, + List zoneCtxList, + RequestType requestType) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return createJobs(createMultipleJobsRequest, zoneCtxList, requestType, null); + } + + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#deleteJobs(java.util.List, java.util.List, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public List> deleteJobs(List jobIDs, + List zoneCtxList, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + List> responses = new ArrayList>(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return responses; + } + + List finalZoneContextList = getFinalZoneCtxList(zoneCtxList, getSIF3Session()); + + // Request operation in all zone/contexts as listed. + for (ZoneContextInfo zoneCtx : finalZoneContextList) + { + ErrorDetails error = allClientChecks(AccessRight.DELETE, AccessType.APPROVED, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + if (error == null) //all good => Send request + { + BulkOperationResponse response = getClient(getConsumerEnvironment()).removeJobs(jobIDs, getHeaderProperties(false, customParameters), urlQueryParameter, zoneCtx.getZone(), zoneCtx.getContext(), requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getMultiObjectClassInfo().getObjectName(), ServiceType.FUNCTIONAL, ResponseAction.DELETE); + responses.add(response); + } + else //pretend to have received a 'fake' error Response + { + responses.add(makeBulkErrorResponse(error)); + } + } + + timer.finish(); + logger.debug("Time taken to call and process 'removeJobs' for "+getServiceURLNamePlural()+": "+timer.timeTaken()+"ms"); + return responses; + } + + /* + * Convenience method. The same as above but without the parameter 'customParameters' which is defaulted to null. + */ + public List> deleteJobs(List jobIDs, + List zoneCtxList, + RequestType requestType) + throws IllegalArgumentException, PersistenceException, ServiceInvokationException + { + return deleteJobs(jobIDs, zoneCtxList, requestType, null); + } + + /*----------------------*/ + /*-- Phase Operations --*/ + /*----------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#retrieveDataFromPhase(sif3.common.model.job.PhaseInfo, javax.ws.rs.core.MediaType, sif3.common.model.PagingInfo, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public Response retrieveDataFromPhase(PhaseInfo phaseInfo, + MediaType returnMimeType, + PagingInfo pagingInfo, + QueryIntention queryIntention, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, requestType); + if (error == null) //all good => Send request + { + // Set default set of HTTP Header fields + HeaderProperties hdrProps = getHeaderProperties(false, customParameters); + + // Add query intention to headers. + addQueryIntentionToHeaders(hdrProps, queryIntention); + + response = getClient(getConsumerEnvironment()).retrieveDataFromPhase(phaseInfo, returnMimeType, pagingInfo, hdrProps, urlQueryParameter, zone, context, requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.QUERY); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'retrive data from phase for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#createDataInPhase(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataRequest, javax.ws.rs.core.MediaType, boolean, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public Response createDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, requestType); + if (error == null) //all good => Send request + { + response = getClient(getConsumerEnvironment()).createDataInPhase(phaseInfo, phaseDataRequest, returnMimeType, useAdvisory, getHeaderProperties(true, customParameters), urlQueryParameter, zone, context, requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.CREATE); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'create data in phase for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#updateDataInPhase(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataRequest, javax.ws.rs.core.MediaType, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public Response updateDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, requestType); + if (error == null) //all good => Send request + { + response = getClient(getConsumerEnvironment()).updateDataInPhase(phaseInfo, phaseDataRequest, returnMimeType, getHeaderProperties(false, customParameters), urlQueryParameter, zone, context, requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.UPDATE); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'update data in phase for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#deleteDataInPhase(sif3.common.model.job.PhaseInfo, sif3.common.ws.job.PhaseDataRequest, javax.ws.rs.core.MediaType, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.header.HeaderValues.RequestType, sif3.common.model.CustomParameters) + */ + @Override + public Response deleteDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, requestType); + if (error == null) //all good => Send request + { + response = getClient(getConsumerEnvironment()).deleteDataInPhase(phaseInfo, phaseDataRequest, returnMimeType, getHeaderProperties(false, customParameters), urlQueryParameter, zone, context, requestType); + + // Set the missing delayed response properties. No need to check if it was delayed request as it is checked in the finaliseDelayedReceipt method. + finaliseDelayedReceipt(response.getDelayedReceipt(), getServiceURLNamePlural(), ServiceType.FUNCTIONAL, ResponseAction.DELETE); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'delete data in phase for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /*----------------------------*/ + /*-- Phase State Operations --*/ + /*----------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#updatePhaseState(sif3.common.model.job.PhaseInfo, sif3.common.CommonConstants.PhaseState, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.CustomParameters) + */ + @Override + public Response updatePhaseState(PhaseInfo phaseInfo, + PhaseState newState, + SIFZone zone, + SIFContext context, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, RequestType.IMMEDIATE); + if (error == null) //all good => Send request + { + response = getClient(getConsumerEnvironment()).updatePhaseState(phaseInfo, newState, getHeaderProperties(false, customParameters), urlQueryParameter, zone, context); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'update phase state for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceConsumer#getPhaseStates(sif3.common.model.job.PhaseInfo, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.CustomParameters) + */ + @Override + public Response getPhaseStates(PhaseInfo phaseInfo, + SIFZone zone, + SIFContext context, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException + { + nullJobNameMethodCheck(getServiceURLNamePlural(), "getServiceURLNamePlural()"); + + Timer timer = new Timer(); + timer.start(); + URLQueryParameter urlQueryParameter = customParameters != null ? customParameters.getQueryParams() : null; + Response response = new Response(); + + if (!getConsumerEnvironment().getIsConnected()) + { + logger.error("No connected environment for "+getConsumerEnvironment().getEnvironmentName()+". See previous erro log entries."); + return response; + } + zone = getFinalZone(zone); + context = getFinalContext(context); + + ErrorDetails error = checkAccessToFunctionalService(getServiceURLNamePlural(), zone, context, RequestType.IMMEDIATE); + if (error == null) //all good => Send request + { + response = getClient(getConsumerEnvironment()).getPhaseStates(phaseInfo, getHeaderProperties(false, customParameters), urlQueryParameter, zone, context); + } + else //pretend to have received a 'fake' error Response + { + response.setError(error); + } + + timer.finish(); + logger.debug("Time taken to call and process 'get phase states for job "+getServiceURLNamePlural()+"/"+phaseInfo.getJobID()+"/"+phaseInfo.getPhaseName()+": "+timer.timeTaken()+"ms"); + + return response; + } + + /*----------------------------------------*/ + /* Implemented Method for Multi-threading */ + /*----------------------------------------*/ + + /** + * This method is all that is needed to run the subscriber in its own thread. + */ + @Override + public final void run() + { +// logger.debug("============================\n Start "+getConsumerName()+" worker thread.\n======================================"); + } + + /*----------------------------*/ + /*-- Other required methods --*/ + /*----------------------------*/ + /** + * This method is is called when the async processor is initialised. It passes all subscription services for the + * given types of service across all zones for the connected environment to this method. It allows the specific + * async consumer implementation to remove some of the subscription services it is not interested in. Basically it allows + * the implementor to filter out un-needed services before the this consumer subscribes to the local queues. Only + * delayed responses for the returned list of service info will then be received by the particular service. Most standard + * implementations would not require any overriding of this method. If a specific implementation wishes to filter out + * some of the environment provided subscriptions then the sub-class of this class should override this method. + * + * @param allServices A list of services for this that is allowed to subscribe delayed responses across the environment of this consumer. + * + * @return The final list of services for which this consumer class shall subscribe to. + */ + public List filterApprovedCRUDServices(List allServices) + { + return allServices; + } + + /* + * Returns all CRUD services for which this consumer has access to. This should be a list of different zones, contexts and service types. + * It returns all FUNCTIONAL services where the consumer has CREATE, DELETE or QUERY as approved in the ACL. Note Functional services do not + * have UPDATE rights. + */ + protected final List getAllApprovedCRUDServices() + { + List allServices = new ArrayList(); + + // Get all Functional Services. Note the service name is the name of the functional service + allServices.addAll(getAllApprovedServicesForRights(getServiceURLNamePlural(), ServiceType.FUNCTIONAL, AccessRight.CREATE, AccessRight.DELETE, AccessRight.QUERY)); + + return filterApprovedCRUDServices(allServices); + } + + /** + * This method is is called when the event processor is initialised. It passes all subscription services for the + * given FUNCTIONAL service across all zones for the connected environment to this method. It allows the specific FUNCTIONAL + * event consumer implementation to remove some of the subscription services it is not interested in. Basically it allows + * the implementor to filter out un-needed subscriptions before the event processor subscribes to the queues. Only + * events for the returned list of service info will then be received by the particular OBJECT service. Most standard + * implementations would not require any overriding of this method. If a specific implementation wishes to filter out + * some of the environment provided subscriptions then the sub-class of this class should override this method. + * + * @param envEventServices A list of services for this that is allowed to subscribe to events across the environment of this consumer. + * + * @return The final list of services for which this consumer class shall subscribe to. + */ + public List filterEventServices(List envEventServices) + { + return envEventServices; + } + + /* + * Return all FUNCTIONAL services that have the right of SUBSCRIBE is set to APPROVED in the ACL. + */ + protected final List getEventServices() + { + //Note the service name is the name of the functional service + return filterEventServices(getAllApprovedServicesForRights(getServiceURLNamePlural(), ServiceType.FUNCTIONAL, AccessRight.SUBSCRIBE)); + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + private JobClient getClient(ConsumerEnvironment envInfo) throws IllegalArgumentException + { + URI baseURI = envInfo.getConnectorBaseURI(ConsumerEnvironment.ConnectorName.servicesConnector); + if (baseURI == null) + { + logger.error(ConsumerEnvironment.ConnectorName.servicesConnector.toString()+" not defined for environment "+envInfo.getEnvironmentName()); + return null; + } + else + { + return new JobClient(ConsumerEnvironmentManager.getInstance(),getServiceURLNamePlural(), getServiceURLNameSingular()); + } + } + + private HeaderProperties getHeaderProperties(boolean isCreateOperation, CustomParameters customParameters) + { + return getHeaderProperties(isCreateOperation, ServiceType.FUNCTIONAL, customParameters); + } + + /* + * Will perform hasAccess() and requestTypeSupported() checks. This is a convenience method, so that not each operation has to + * call the two methods sequentially and manage all the flow. + */ + private ErrorDetails allClientChecks(AccessRight right, AccessType accessType, SIFZone zone, SIFContext context, RequestType requestType) + { + return allClientChecks(getServiceURLNamePlural(), ServiceType.FUNCTIONAL, right, accessType, zone, context, requestType); + } + + private void nullJobNameMethodCheck(String serviceURLName, String methodName) throws IllegalArgumentException + { + if (StringUtils.isEmpty(serviceURLName)) + { + throw new IllegalArgumentException(methodName+" method not implemented correctly. Returns null or empty which is invalid."); + } + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/BaseConsumer.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/BaseConsumer.java new file mode 100644 index 00000000..6e1ec2e9 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/BaseConsumer.java @@ -0,0 +1,632 @@ +/* + * BaseConsumer.java + * Created: 12 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.consumer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.AdvancedProperties; +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues; +import sif3.common.header.HeaderValues.QueryIntention; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.header.RequestHeaderConstants; +import sif3.common.interfaces.MinimalConsumer; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; +import sif3.common.model.CustomParameters; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; +import sif3.common.model.ZoneContextInfo; +import sif3.common.model.delayed.DelayedRequestReceipt; +import sif3.common.model.delayed.DelayedResponseReceipt; +import sif3.common.persist.model.SIF3Session; +import sif3.common.ws.BaseResponse; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.ErrorDetails; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; +import sif3.infra.common.env.types.ConsumerEnvironment; +import sif3.infra.rest.queue.LocalConsumerQueue; +import sif3.infra.rest.queue.LocalMessageConsumer; +import sif3.infra.rest.queue.QueueReaderInfo; + +/** + * It is expected that all consumer implementations extend this class. It has a set of abstract methods that need to be implemented by + * a specific consumer class. It is not expected that a developer extends this class. There are a set of standard Abstract Consumers the + * developer will implement. This class however forms the base of these Abstract Consumer classes. + * + * @author Joerg Huber + * + */ +public abstract class BaseConsumer implements MinimalConsumer +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /* Below variables are for testing purposes only */ + private static Boolean testMode = null; + /* End Testing variables */ + + private boolean checkACL = true; + + /* The next two properties are used for delayed responses or events */ + private LocalConsumerQueue localConsumerQueue = null; +// private ExecutorService service = null; + + private QueueReaderInfo service = null; + + /*-------------------------------------------------------------*/ + /* Abstract method relating to general Consumer functionality. */ + /*-------------------------------------------------------------*/ + + /** + * This method is called when a consumer service is shut down. It can be used to free up internally allocated resources + * as well as clean-up other things. + */ + public abstract void shutdown(); + + /** + * This method is called when a delayed error response is retrieved from the consumer's queue.

+ * + * @see sif3.common.interfaces.DelayedConsumer#onError(sif3.common.ws.ErrorDetails, sif3.common.model.delayed.DelayedResponseReceipt) + * + * @param error See onError() method in DelayedConsumer class. + * @param receipt See onError() method in DelayedConsumer class. + */ + public abstract void processDelayedError(ErrorDetails error, DelayedResponseReceipt receipt); + + /** + * Constructor. + */ + public BaseConsumer() + { + super(); + + // Set some properties at this stage for simplicity reasons. + checkACL = getConsumerEnvironment().getCheckACL(); + + if (getConsumerEnvironment().getEventsEnabled() || getConsumerEnvironment().getDelayedEnabled()) + { + logger.debug("Events and/or Delayed Responses enabled => start local consumer queue for "+getConsumerName()); + createLocalConsumerQueue(); + } + else + { + logger.debug("Events AND Delayed Responses are disabled. Local consumer queues and threads are not started."); + } + } + + /*-------------------------------*/ + /* Useful 'Housekeeping' methods */ + /*-------------------------------*/ + /** + * @return Returns the actual Class Name of this consumer + */ + public String getConsumerName() + { + return getClass().getSimpleName(); + } + + /** + * Utility method. Mainly used for useful logging messages. + * + * @return Returns the Adapter Name as defined in the adapter.id property of the consumer property file concatenated with the + * Consumer Name (class name) + */ + public String getPrettyName() + { + return getConsumerEnvironment().getAdapterName()+" - " + getConsumerName(); + } + + /** + * Utility method to easily retrieve the consumer environment configuration. + * + * @return See desc + */ + public ConsumerEnvironment getConsumerEnvironment() + { + return (ConsumerEnvironment)ConsumerEnvironmentManager.getInstance().getEnvironmentInfo(); + } + + /** + * Utility method to easily retrieve the property file content for a consumer. + * + * @return See desc + */ + public AdvancedProperties getServiceProperties() + { + return ConsumerEnvironmentManager.getInstance().getServiceProperties(); + } + + /** + * @return Returns the Service Name. + */ + public String getServiceName() + { + return getMultiObjectClassInfo().getObjectName(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + * Start of 'Dynamic' HTTP Header Field override section + * + * The following set of methods are used for a more configurable way how some HTTP header parameters are set. + * By default the following HTTP Header fields are retrieved from the consumer's property file and put in corresponding + * HTTP Header Fields: + * + * Property HTTP Header + * ------------------------------------------------ + * adapter.generator.id generatorId + * env.application.key applicationKey + * env.userToken authenticatedUser + * env.mediaType Content-Type, Accept + * adapter.mustUseAdvisoryIDs mustUseAdvisory + * adapter.compression.enabled Content-Encoding, Accept-Encoding + * + * Only properties that are not null or empty string will be set in the corresponding HTTP Header. + * + * There are situations where and application may need a more 'dynamic' behaviour where the above values are determined + * at runtime, based on other circumstances and therefore these properties must be retrieved from an other source than the + * consumer's property file. In such a case the methods below can be overwritten to make them dynamic and controlled by + * the implementation rather than driven by the consumer's property file. If any of the methods below is overwritten then + * the value of the over riding method is set in the corresponding HTTP Header field if the return value of the method + * is not null or an empty string. + *------------------------------------------------------------------------------------------------------------------------*/ + + /** + * This method returns the value of the adapter.generator.id property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The adapter.generator.id property from the consumer's property file + */ + public String getGeneratorID() + { + return getConsumerEnvironment().getGeneratorID(); + } + + /** + * This method returns the value of the env.application.key property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.application.key property from the consumer's property file + */ + public String getApplicationKey() + { + return getConsumerEnvironment().getEnvironmentKey().getApplicationKey(); + } + + /** + * This method returns the value of the env.userToken property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.userToken property from the consumer's property file + */ + public String getAuthentictedUser() + { + return getConsumerEnvironment().getEnvironmentKey().getUserToken(); + } + + /** + * This method returns the value of the env.mediaType property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.mediaType property from the consumer's property file + */ + public MediaType getRequestMediaType() + { + return getConsumerEnvironment().getMediaType(); + } + + /** + * This method returns the value of the env.mediaType property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.mediaType property from the consumer's property file + */ + public MediaType getResponseMediaType() + { + return getConsumerEnvironment().getMediaType(); + } + + /** + * This method returns the value of the adapter.mustUseAdvisoryIDs property from the consumer's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The adapter.mustUseAdvisoryIDs property from the consumer's property file + */ + public boolean getMustUseAdvisory() + { + return getConsumerEnvironment().getUseAdvisory(); + } + + /** + * This method returns the value of the adapter.compression.enabled property from the consumer's property file. If + * that needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The adapter.compression.enabled property from the consumer's property file + */ + public boolean getCompressionEnabled() + { + return getConsumerEnvironment().getCompressionEnabled(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + * End of 'Dynamic' HTTP Header Field override section + *-----------------------------------------------------------------------------------------------------------------------*/ + + /*------------------------------*/ + /* Some Getter & Setter methods */ + /*------------------------------*/ + + public final LocalConsumerQueue getLocalConsumerQueue() + { + return localConsumerQueue; + } + + public final void setLocalConsumerQueue(LocalConsumerQueue localConsumerQueue) + { + this.localConsumerQueue = localConsumerQueue; + } + + /*-------------------*/ + /* Interface Methods */ + /*-------------------*/ + /* + * (non-Javadoc) + * @see sif3.common.consumer.Consumer#finalise() + */ + @Override + public void finalise() + { + logger.debug("Shut down all Local Consumer Thread Pool for "+getConsumerName()); + if (service != null) + { + for (LocalMessageConsumer localReader : service.getLinkedClasses()) + { + localReader.shutdown(); + } + + service.getService().shutdown(); + } + shutdown(); + } + + + /*------------------------------------------------------------------------------------*/ + /*-- Methods for Local Queues and Threads for Delayed Response and Event management --*/ + /*------------------------------------------------------------------------------------*/ + + /** + * Only creates the local consumer queue if it doesn't already exist. The queue (new or existing) is then returned. + * + * @return See desc. + */ + public final LocalConsumerQueue createLocalConsumerQueue() + { + if (getLocalConsumerQueue() == null) + { + // Create local queue with the capacity indicated with the consumer config + logger.debug("Create Local Queue for "+getConsumerName()); + + // Use the local queue as a trigger of threads rather than actual queueing of messages. Use 1 as the minimum + setLocalConsumerQueue(new LocalConsumerQueue(1, getClass().getSimpleName() + "LocalQueue", getClass().getSimpleName())); + startListenerThreads(); + } + return getLocalConsumerQueue(); + } + + /* + * Will initialise the threads and add them to the local consumer queue. + */ + private void startListenerThreads() + { + // Start up all consumers for this subscriber. + int numThreads = getNumOfConsumerThreads(); + logger.debug("Start "+numThreads+" "+getConsumerName()+" threads."); + logger.debug("Total number of threads before starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); + + service = new QueueReaderInfo(Executors.newFixedThreadPool(numThreads), new ArrayList()); + for (int i = 0; i < numThreads; i++) + { + String consumerID = getConsumerName()+" "+(i+1); + logger.debug("Start Consumer "+consumerID); + LocalMessageConsumer consumer = new LocalMessageConsumer(getLocalConsumerQueue(), consumerID, this); + service.getLinkedClasses().add(consumer); + service.getService().execute(consumer); + } + logger.debug(numThreads+" "+getConsumerName()+" initilaised and started."); + logger.debug("Total number of threads after starting Local Queue for "+getConsumerName()+" "+Thread.activeCount()); + } + + private final int getNumOfConsumerThreads() + { + return getServiceProperties().getPropertyAsInt("consumer.local.workerThread", getClass().getSimpleName(), 1); + } + + /*--------------------------------------------------------------------------------*/ + /*-- Protected Methods to be used by Abstract Consumers that extend this class. --*/ + /*--------------------------------------------------------------------------------*/ + + protected SIF3Session getSIF3Session() + { + return ConsumerEnvironmentManager.getInstance().getSIF3Session(); + } + + protected HeaderProperties getHeaderProperties(boolean isCreateOperation, HeaderValues.ServiceType serviceType, CustomParameters customParameters) + { + HeaderProperties hdrProps = new HeaderProperties(); + + // First we add all Custom HTTP Headers. We add SIF defined HTTP header later. This will also ensure that we + // will override custom properties with SIF defined properties. + if ((customParameters != null) && (customParameters.getHttpHeaderParams() != null)) + { + hdrProps = customParameters.getHttpHeaderParams(); + } + + // Now we set SIF defined HTTP headers... + + // Set the remaining header fields for this type of request + if (isCreateOperation) + { + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, (getMustUseAdvisory() ? "true" : "false")); + } + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_SERVICE_TYPE, serviceType.name()); + + // Set values of consumer property file or their overridden value. Note thsetHeaderProperty() method will do the check + // for null, so no need to do this here. + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_APPLICATION_KEY, getApplicationKey()); + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTHENTICATED_USER, getAuthentictedUser()); + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_GENERATOR_ID, getGeneratorID()); + + return hdrProps; + } + + protected ErrorDetails hasAccess(String serviceName, ServiceType serviceType, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context) + { + ErrorDetails error = null; + if (checkACL) + { + if (!getSIF3Session().hasAccess(right, accessType, serviceName, serviceType, zone, context)) + { + String zoneID = (zone == null) ? "Default" : zone.getId(); + String contextID = (context == null) ? "Default" : context.getId(); + error = new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Consumer is not authorized to issue the requested operation.", right.name()+ " access is not set to "+accessType.name()+" for the service " + serviceName +" and the given zone ("+zoneID+") and context ("+contextID+") in the environment "+getSIF3Session().getEnvironmentName(), "Client side check."); + } + } + return error; + } + + protected ErrorDetails allClientChecks(String serviceName, ServiceType serviceType, AccessRight right, AccessType accessType, SIFZone zone, SIFContext context, RequestType requestType) + { + ErrorDetails error = hasAccess(serviceName, serviceType, right, accessType, zone, context); + if ((error == null) && (requestType != null)) + { + error = requestTypeEnabled(requestType); + } + return error; + } + + protected ErrorDetails checkAccessToFunctionalService(String serviceName, SIFZone zone, SIFContext context, RequestType requestType) + { + ErrorDetails error = null; + ServiceInfo serviceInfo = getSIF3Session().getServiceInfoForService(zone, context, serviceName, ServiceType.FUNCTIONAL); + if (serviceInfo == null) // Found no service for the given functional service name. + { + error = new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Access Denied.", "Consumer is not authorized to access the functional service '"+serviceName+"'.", "Consumer side check."); + } + + if ((error == null) && (requestType != null)) + { + error = requestTypeEnabled(requestType); + } + return error; + } + + protected ErrorDetails requestTypeEnabled(RequestType requestType) + { + ErrorDetails error = null; + + if ((requestType == RequestType.DELAYED) && (!getConsumerEnvironment().getDelayedEnabled())) + { + error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Client side Check: DELAYED requests are not enabled."); + } + return error; + } + + protected BulkOperationResponse makeBulkErrorResponseForCreates(ErrorDetails error) + { + BulkOperationResponse response = new BulkOperationResponse(); + setErrorDetails(response, error); + return response; + } + + protected BulkOperationResponse makeBulkErrorResponse(ErrorDetails error) + { + BulkOperationResponse response = new BulkOperationResponse(); + setErrorDetails(response, error); + return response; + } + + protected Response createErrorResponse(ErrorDetails error) + { + Response response = new Response(); + setErrorDetails(response, error); + return response; + } + + /* + * This method sets the remaining properties in the receipt for delayed responses. There are a few fields that cannot be set at the ObjectServiceClient as + * they are not known or cannot be determined in there but are well known in the abstract consumer. + */ + protected void finaliseDelayedReceipt(DelayedRequestReceipt delayedReceipt, String serviceName, ServiceType serviceType, ResponseAction requestedAction) + { + if (delayedReceipt != null) + { + //delayedReceipt.setRequestDate(requestDate); + delayedReceipt.setServiceName(serviceName); + delayedReceipt.setServiceType(serviceType); + delayedReceipt.setRequestedAction(requestedAction); + } + } + + protected void nullMethodCheck(Object objectToCheck, String methodName) throws IllegalArgumentException + { + if (objectToCheck == null) + { + throw new IllegalArgumentException(methodName+" method not implemented correctly. Returns null which is invalid."); + } + } + + /* + * This method checks if the test.testmode in the consumer's property file is set to TRUE. + */ + protected boolean isTestMode() + { + if (testMode == null) + { + AdvancedProperties props = getServiceProperties(); + testMode = props.getPropertyAsBool("test.testmode", false); + } + return testMode; + } + + protected List getFinalZoneCtxList( List zoneCtxList, SIF3Session sif3Session) + { + List finalZoneContextList = null; + + if (zoneCtxList == null) + { + finalZoneContextList = new ArrayList(); + } + else + { + finalZoneContextList = zoneCtxList; + } + + if (finalZoneContextList.size() == 0) //add default context and zone + { + // Set zone and context to null which will ensure that the matrix params won't be set and therefore the provider will assume default context & zone + finalZoneContextList.add(new ZoneContextInfo((SIFZone)null, (SIFContext)null)); + } + + // Check all entries and if 'null' is used as zone or context then we assign the default. + for (ZoneContextInfo zoneCtxInfo : finalZoneContextList) + { + // If zone or zone ID is null then we set the default zone. + if ((zoneCtxInfo.getZone() == null) || StringUtils.isEmpty(zoneCtxInfo.getZone().getId())) + { + zoneCtxInfo.setZone(null); // won't set matrix parameter which means default zone + } + // If context or context ID is null then we set the default zone. + if ((zoneCtxInfo.getContext() == null) || StringUtils.isEmpty(zoneCtxInfo.getContext().getId())) + { + zoneCtxInfo.setContext(null); // won't set matrix parameter which means default context + } + } + + return finalZoneContextList; + } + + protected SIFZone getFinalZone(SIFZone zone) + { + // If zone or zone ID is null then we set the default zone. This means set it to null which defaults to the default zone + if ((zone == null) || StringUtils.isEmpty(zone.getId())) + { + return null; // won't set matrix parameter which means default zone + } + + return zone; + } + + protected SIFContext getFinalContext(SIFContext context) + { + // If context or context ID is null then we set the default context. This means set it to null which defaults to the default context + if ((context == null) || StringUtils.isEmpty(context.getId())) + { + return null; // won't set matrix parameter which means default context + } + + return context; + } + + protected void addQueryIntentionToHeaders(HeaderProperties hdrProps, QueryIntention queryIntention) + { + // Ensure query Intention is not null. if so default to ONE-OFF as per SIF 3.x spec. + queryIntention = (queryIntention == null) ? QueryIntention.ONE_OFF : queryIntention; + + // Add query intention to headers. + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_QUERY_INTENTION, queryIntention.getHTTPHeaderValue()); + } + + + /* + * Returns all services for which this consumer has APPROVE access to at least on of the rights listed. This should be a list of different zones, + * contexts and service types. + */ + protected final List getAllApprovedServicesForRights(String serviceName, ServiceType serviceType, AccessRight... rights) + { + SIF3Session sif3Session = ConsumerEnvironmentManager.getInstance().getSIF3Session(); + List allServices = new ArrayList(); + + // Get Services for given type and name + List services = sif3Session.getServiceInfoForService(serviceName, serviceType); + for (ServiceInfo serviceInfo : services) + { + // Check if any of the given rights states APPROVED + if (rights != null) + { + for (AccessRight right : rights) + { + if (serviceInfo.getRights().hasRight(right, AccessType.APPROVED)) + { + allServices.add(serviceInfo); //found one. + + // we can stop as the service has at least one of the rights approved. + break; + } + } + } + } + return allServices; + } + + /*-------------------*/ + /* Private Methods --*/ + /*-------------------*/ + + private void setErrorDetails(BaseResponse response, ErrorDetails errorDetails) + { + response.setStatus(errorDetails.getErrorCode()); + response.setStatusMessage(errorDetails.getMessage()); + response.setError(errorDetails); + response.setContentLength(0); + response.setHasEntity(false); + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/ConsumerLoader.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/ConsumerLoader.java index 9297eafe..6d532857 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/ConsumerLoader.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/consumer/ConsumerLoader.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.slf4j.Logger; @@ -27,6 +26,7 @@ import au.com.systemic.framework.utils.AdvancedProperties; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants; import sif3.common.exception.PersistenceException; import sif3.common.interfaces.HibernateProperties; import sif3.common.model.ServiceInfo; @@ -37,6 +37,7 @@ import sif3.infra.common.interfaces.EnvironmentConnector; import sif3.infra.rest.env.connectors.EnvironmentConnectorFactory; import sif3.infra.rest.queue.LocalConsumerQueue; +import sif3.infra.rest.queue.QueueReaderInfo; import sif3.infra.rest.queue.RemoteMessageQueueReader; import sif3.infra.rest.queue.connectors.ConsumerQueueConnector; import sif3.infra.rest.queue.connectors.ConsumerSubscriptionConnector; @@ -69,9 +70,10 @@ public class ConsumerLoader private ConsumerSubscriptionConnector subscriptionConnector = null; private List> eventConsumers = new ArrayList>(); private List crudConsumers = new ArrayList(); + private List fsServiceConsumers = new ArrayList(); - private List msgReaderServices = new ArrayList(); - + private List> msgReaderServices = new ArrayList>(); + /** * Initialises the consumer based on the given property file. If anything fails to initialise then an error is logged and this method * returns false. The consumer should not really continue in such a case as its behaviour is not defined and most likely will throw @@ -202,11 +204,16 @@ private void shutdownConsumer() ConsumerEnvironmentManager envMgr = ConsumerEnvironmentManager.getInstance(); logger.debug("Shut down all remote message queue reader threads..."); - for (ExecutorService service : msgReaderServices) + for (QueueReaderInfo remoteReaderInfo : msgReaderServices) { - service.shutdown(); + for (RemoteMessageQueueReader remoteReader : remoteReaderInfo.getLinkedClasses()) + { + remoteReader.shutdown(); + } + + remoteReaderInfo.getService().shutdown(); } - + logger.debug("Shut down each consumer ..."); for (AbstractConsumer consumer : crudConsumers) { @@ -220,7 +227,13 @@ private void shutdownConsumer() consumer.finalise(); } - if (getConsumerEnvironment().getEventsEnabled() || (getConsumerEnvironment().getDelayedEnabled())) + for (AbstractFunctionalServiceConsumer consumer : fsServiceConsumers) + { + logger.debug("Shutdown " + consumer.getClass().getSimpleName()); + consumer.finalise(); + } + + if (getConsumerEnvironment().getEventsEnabled() || (getConsumerEnvironment().getDelayedEnabled())) { logger.debug("Shutdown Event Subscription Connector..."); if (subscriptionConnector != null) @@ -245,6 +258,10 @@ private void shutdownConsumer() logger.debug("Release DB Connections...."); HibernateUtil.shutdown(); + + // All done but some background threads may not have fully shut down (eg. ExecutorServices). These may sleep + // up to a given time CommonConstants.MAX_SLEEP_MILLISEC. So let's quickly wait for them and the conclude... + doWait(CommonConstants.MAX_SLEEP_MILLISEC); } private ConsumerEnvironment getConsumerEnvironment() @@ -264,28 +281,36 @@ private boolean initAsyncProcessor() List allLocalQueueEventServices = new ArrayList(); List allLocalQueueCRUDServices = new ArrayList(); - for (AbstractEventConsumer consumer : eventConsumers) - { - // Get Event services for this consumer - if (getConsumerEnvironment().getEventsEnabled()) - { - addServices(allLocalQueueEventServices, consumer.getEventServices(), consumer.getLocalConsumerQueue()); - } - - // Get CRUD services for this consumer - if (getConsumerEnvironment().getDelayedEnabled()) - { - addServices(allLocalQueueCRUDServices, consumer.getAllApprovedCRUDServices(), consumer.getLocalConsumerQueue()); - } - } + // Get all services for all Event ONLY consumers + if (getConsumerEnvironment().getEventsEnabled()) + { + for (AbstractEventConsumer consumer : eventConsumers) + { + addServices(allLocalQueueEventServices, consumer.getEventServices(), consumer.getLocalConsumerQueue()); + } + for (AbstractFunctionalServiceConsumer consumer : fsServiceConsumers) + { + addServices(allLocalQueueEventServices, consumer.getEventServices(), consumer.getLocalConsumerQueue()); + } + } - // Get all services for all CRUD ONLY consumers + // Get all services for all CRUD consumers if (getConsumerEnvironment().getDelayedEnabled()) { for (AbstractConsumer consumer : crudConsumers) { addServices(allLocalQueueCRUDServices, consumer.getAllApprovedCRUDServices(), consumer.getLocalConsumerQueue()); } + + for (AbstractFunctionalServiceConsumer consumer : fsServiceConsumers) + { + addServices(allLocalQueueCRUDServices, consumer.getAllApprovedCRUDServices(), consumer.getLocalConsumerQueue()); + } + + for (AbstractEventConsumer consumer : eventConsumers) + { + addServices(allLocalQueueCRUDServices, consumer.getAllApprovedCRUDServices(), consumer.getLocalConsumerQueue()); + } } if (logger.isDebugEnabled()) @@ -347,20 +372,23 @@ private boolean startReadingRemoteQueues(HashMap queuesInfos) /* * Will initialise the threads and add them to the local consumer queue. + * */ - private ExecutorService startRemoteMessageReaderThreads(QueueInfo queueInfo, int numThreads) + private QueueReaderInfo startRemoteMessageReaderThreads(QueueInfo queueInfo, int numThreads) { String remoteQueueName = getRemoteQueueName(queueInfo); logger.debug("Start "+numThreads+" message readers for "+remoteQueueName); logger.debug("Total number of threads before starting message readers for "+remoteQueueName+" "+Thread.activeCount()); - ExecutorService service = Executors.newFixedThreadPool(numThreads); + + QueueReaderInfo queueReaderInfo = new QueueReaderInfo(Executors.newFixedThreadPool(numThreads), new ArrayList()); for (int i = 0; i < numThreads; i++) { try { RemoteMessageQueueReader remoteReader = new RemoteMessageQueueReader(queueInfo, i); logger.debug("Start Remote Reader "+remoteQueueName+" "+i); - service.execute(remoteReader); + queueReaderInfo.getLinkedClasses().add(remoteReader); + queueReaderInfo.getService().execute(remoteReader); } catch (Exception ex) { @@ -369,7 +397,7 @@ private ExecutorService startRemoteMessageReaderThreads(QueueInfo queueInfo, int } logger.debug(numThreads+" "+remoteQueueName+" message readers initilaised and started."); logger.debug("Total number of threads after starting message readers for "+remoteQueueName+" "+Thread.activeCount()); - return service; + return queueReaderInfo; } private void initialiseConsumers(AdvancedProperties adapterProps) @@ -400,9 +428,14 @@ else if (classObj instanceof AbstractConsumer) logger.debug("Added " + classObj.getClass().getSimpleName() + " to crudConsumer only list"); crudConsumers.add((AbstractConsumer) classObj); } + else if (classObj instanceof AbstractFunctionalServiceConsumer) + { + logger.debug("Added " + classObj.getClass().getSimpleName() + " to fsServiceConsumers list"); + fsServiceConsumers.add((AbstractFunctionalServiceConsumer) classObj); + } else { - logger.error("Consumer class " + className + " doesn't extend AbstractConsumer or AbstractEventConsumer. Cannot initialse the Consumer."); + logger.error("Consumer class " + className + " doesn't extend AbstractConsumer, AbstractEventConsumer of AbstractFunctionalServiceConsumer. Cannot initialse the Consumer."); } } catch (Exception ex) @@ -429,4 +462,19 @@ private String getRemoteQueueName(QueueInfo queueInfo) { return queueInfo.getQueue().getName()+" ("+queueInfo.getQueue().getQueueID()+")"; } + + private void doWait(long millisecs) + { + Object semaphore = new Object(); + synchronized (semaphore) + { + try + { + semaphore.wait(millisecs); + } + catch (InterruptedException ex) + { + } + } + } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseEventProvider.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseEventProvider.java index 66e0eb66..52c7a063 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseEventProvider.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseEventProvider.java @@ -20,27 +20,21 @@ import java.util.List; -import javax.ws.rs.core.MediaType; - import sif3.common.CommonConstants; import sif3.common.header.HeaderProperties; import sif3.common.header.HeaderValues.EventAction; import sif3.common.header.HeaderValues.ServiceType; -import sif3.common.header.RequestHeaderConstants; import sif3.common.interfaces.EventProvider; import sif3.common.interfaces.SIFEventIterator; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.SIFContext; import sif3.common.model.SIFEvent; import sif3.common.model.SIFZone; import sif3.common.model.ServiceInfo; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; import sif3.common.model.ZoneContextInfo; import sif3.common.persist.model.SIF3Session; -import sif3.common.ws.BaseResponse; import sif3.infra.common.env.mgr.BrokeredProviderEnvironmentManager; -import sif3.infra.common.env.mgr.ProviderManagerFactory; -import sif3.infra.common.interfaces.EnvironmentManager; import sif3.infra.rest.client.EventClient; @@ -74,103 +68,6 @@ public int getMaxObjectsInEvent() return getServiceProperties().getPropertyAsInt(CommonConstants.EVENT_MAX_OBJ, getProviderName(), 10); } - /*------------------------------------------------------------------------------------------------------------------------ - * Start of 'Dynamic' HTTP Header Field override section for Events - * - * The following set of methods are used for a more configurable way how some HTTP header parameters are set. - * By default the following HTTP Header fields are retrieved from the provider's property file and put in corresponding - * HTTP Header Fields of each event: - * - * Property HTTP Header - * ---------------------------------------------------------------- - * adapter.generator.id generatorId - * env.application.key applicationKey - * env.userToken authenticatedUser - * env.mediaType Content-Type, Accept - * adapter.compression.enabled Content-Encoding, Accept-Encoding - * - * Only properties that are not null or empty string will be set in the corresponding HTTP Header. - * - * There are situations where and application may need a more 'dynamic' behaviour where the above values are determined - * at runtime, based on other circumstances and therefore these properties must be retrieved from an other source than the - * providers's property file. In such a case the methods below can be overwritten to make them dynamic and controlled by - * the implementation rather than driven by the provider's property file. If any of the methods below is overwritten then - * the value of the over riding method is set in the corresponding HTTP Header field if the return value of the method - * is not null or an empty string. - *------------------------------------------------------------------------------------------------------------------------*/ - - /** - * This method returns the value of the adapter.generator.id property from the provider's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The adapter.generator.id property from the provider's property file - */ - public String getGeneratorID() - { - return getProviderEnvironment().getGeneratorID(); - } - - /** - * This method returns the value of the env.application.key property from the provider's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.application.key property from the provider's property file - */ - public String getApplicationKey() - { - return getProviderEnvironment().getEnvironmentKey().getApplicationKey(); - } - - /** - * This method returns the value of the env.userToken property from the provider's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.userToken property from the provider's property file - */ - public String getAuthentictedUser() - { - return getProviderEnvironment().getEnvironmentKey().getUserToken(); - } - - /** - * This method returns the value of the env.mediaType property from the provider's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.mediaType property from the provider's property file - */ - public MediaType getRequestMediaType() - { - return getProviderEnvironment().getMediaType(); - } - - /** - * This method returns the value of the env.mediaType property from the provider's property file. If that - * needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The env.mediaType property from the provider's property file - */ - public MediaType getResponseMediaType() - { - return getProviderEnvironment().getMediaType(); - } - - /** - * This method returns the value of the adapter.compression.enabled property from the provider's property file. If - * that needs to be overridden by a specific implementation then the specific sub-class should override this method. - * - * @return The adapter.compression.enabled property from the provider's property file - */ - public boolean getCompressionEnabled() - { - return getProviderEnvironment().getCompressionEnabled(); - } - - - /*------------------------------------------------------------------------------------------------------------------------ - * End of 'Dynamic' HTTP Header Field override section - *-----------------------------------------------------------------------------------------------------------------------*/ - - /** * This method retrieves all events to be published by calling the abstract method getSIFEvents(). The returned list * is then broadcasted to all zones known to the implementing agent. @@ -184,13 +81,14 @@ public void broadcastEvents() int failedRecords = 0; int maxNumObjPerEvent = getMaxObjectsInEvent(); - SIF3Session sif3Session = getActiveSession(); - if (sif3Session == null) + BrokeredProviderEnvironmentManager envMgr = getBrokeredEnvironmentManager(); + if (envMgr == null) // not a brokered environment. Cannot sent events! { - return; //cannot send events. Error already logged. + return; // error already logged. } - List servicesForProvider = getServicesForProvider(sif3Session); + SIF3Session sif3Session = envMgr.getSIF3Session(); + List servicesForProvider = getServicesForProvider(sif3Session, ServiceType.OBJECT); // If there are no services for this provider defined then we don't need to get any events at all. if ((servicesForProvider == null) || (servicesForProvider.size() == 0)) @@ -202,7 +100,7 @@ public void broadcastEvents() { // Let's get the Event Client String serviceName = getServiceName(); - EventClient evtClient = new EventClient(getEnvironmentManager(), getRequestMediaType(), getResponseMediaType(), serviceName, getMarshaller(), getCompressionEnabled()); + EventClient evtClient = new EventClient(envMgr, getRequestMediaType(), getResponseMediaType(), serviceName, getMarshaller(), getCompressionEnabled()); SIFEventIterator iterator = getSIFEvents(); if (iterator != null) { @@ -227,7 +125,7 @@ public void broadcastEvents() for (ServiceInfo service : servicesForProvider) { // Check if provider has rights to publish to given zone and context - if (hasAccess(service)) + if (hasAccess(service, AccessRight.PROVIDE, AccessType.APPROVED)) { failedRecords = failedRecords + prepareEventAndSend(evtClient, sifEvents, service.getZone(), service.getContext(), eventAction); } @@ -242,7 +140,7 @@ public void broadcastEvents() for (ZoneContextInfo zoneCtxInfo : sifEvents.getLimitToZoneCtxList()) { // Check if provider has rights to publish to given zone and context - if (sif3Session.hasAccess(AccessRight.PROVIDE, AccessType.APPROVED, serviceName, zoneCtxInfo.getZone(), zoneCtxInfo.getContext())) + if (sif3Session.hasAccess(AccessRight.PROVIDE, AccessType.APPROVED, serviceName, null, zoneCtxInfo.getZone(), zoneCtxInfo.getContext())) { failedRecords = failedRecords + prepareEventAndSend(evtClient, sifEvents, zoneCtxInfo.getZone(), zoneCtxInfo.getContext(), eventAction); } @@ -297,7 +195,7 @@ protected int prepareEventAndSend(EventClient evtClient, SIFEvent sifEvents, //Just in case the developer has changed it. Should not be allowed :-) modifiedEvents.setEventAction(eventAction); - if (!sendEvents(evtClient, modifiedEvents, zone, context, customHTTPHeaders)) + if (!sendEvents(evtClient, modifiedEvents, zone, context, ServiceType.OBJECT, customHTTPHeaders)) { //Report back to the caller. This should also give the event back to the caller. onEventError(modifiedEvents, zone, context); @@ -307,126 +205,4 @@ protected int prepareEventAndSend(EventClient evtClient, SIFEvent sifEvents, return failedRecords; } - - private void logNoAccessRight(SIFZone zone, SIFContext context) - { - String zoneID = (zone == null) ? "Default" : zone.getId(); - String contextID = (context == null) ? CommonConstants.DEFAULT_CONTEXT_NAME : context.getId(); - - logger.debug("The "+getProviderName()+" does not have the ACL entry PROVIDE = APPROVED for service = " + getServiceName() + " for the Zone = "+ zoneID + " and the Context = " + contextID + ". Events are NOT sent to this zone and context."); -// return ((sifEvents != null) ? sifEvents.getListSize() : 0); - } - - - /** - * If one doesn't want certain events to be published to a given zone then this method needs to be - * overridden. It allows to test for the event and zone and make the appropriate decision if the event - * shall be sent. - * - * @param event The event to be published to the zone. - * @param zone The zone to which the event is published to. - * @param customHTTPHeaders Custom HTTP Headers to be added to the event. - * - * @return TRUE: Event sent successfully. FALSE: Failed to send event. Error must be logged. - */ - protected boolean sendEvents(EventClient evtClient, SIFEvent sifEvents, SIFZone zone, SIFContext context, HeaderProperties customHTTPHeaders) - { - logger.debug(getPrettyName()+" sending a "+getServiceName()+" event with "+sifEvents.getListSize()+" sif objects."); - try - { - if (logger.isDebugEnabled()) - { - logger.debug("Custom HTTP Headers set by modifyBeforePublishing() method: "+customHTTPHeaders); - } - if (customHTTPHeaders == null) - { - customHTTPHeaders = new HeaderProperties(); - } - - // Add all other HTTP headers to this customHTTPHeader. This ensures that SIF managed HTTP headers will - // override custom HTTP Headers. - addSIF3OverrideHeaderProperties(customHTTPHeaders); - - BaseResponse response = evtClient.sendEvents(sifEvents, zone, context, customHTTPHeaders); - if (response.hasError()) - { - logger.error("Failed to send event: "+response.getError()); - return false; - } - else - { - return true; - } - } - catch (Exception ex) - { - logger.error("An error occured sending an event message. See previous error log entries.", ex); - return false; - } - } - - - protected void addSIF3OverrideHeaderProperties(HeaderProperties hdrProps) - { - //HeaderProperties hdrProps = new HeaderProperties(); - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_GENERATOR_ID, getGeneratorID()); - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_APPLICATION_KEY, getApplicationKey()); - hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTHENTICATED_USER, getAuthentictedUser()); - - //return hdrProps; - } - - - private BrokeredProviderEnvironmentManager getEnvironmentManager() - { - EnvironmentManager envMgr = ProviderManagerFactory.getEnvironmentManager(); - if (envMgr != null) // we have a proper setup and are initialised. - { - // Note: Currently we only support events for Brokered Environments, so the EnvironmentManager should be of type - // BrokeredProviderEnvironmentManager - if (envMgr instanceof BrokeredProviderEnvironmentManager) - { - return (BrokeredProviderEnvironmentManager)envMgr; - } - else - { - logger.error("Events are only supported for BROKERED environments. This provider is a DIRECT Environment."); - } - } - else - { - logger.error("Environment Manager not initialised. Not connected to an environment. No Environment Manger available."); - } - - // If we get here then we don't have a valid environment manager. - return null; - } - - - private List getServicesForProvider(SIF3Session sif3Session) - { - if (sif3Session != null) - { - return sif3Session.getServiceInfoForService(getServiceName(), ServiceType.OBJECT); - } - - return null; - } - - private SIF3Session getActiveSession() - { - BrokeredProviderEnvironmentManager envMgr = getEnvironmentManager(); - if (envMgr != null) - { - return envMgr.getSIF3Session(); - } - - return null; // Error already logged. - } - - private boolean hasAccess(ServiceInfo service) - { - // All that is required is PROVIDE right to be set to APPROVED. - return service.getRights().hasRight(AccessRight.PROVIDE, AccessType.APPROVED); - } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseFunctionalServiceProvider.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseFunctionalServiceProvider.java new file mode 100644 index 00000000..bb909b2c --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseFunctionalServiceProvider.java @@ -0,0 +1,628 @@ +/* + * BaseFunctionalServiceProvider.java + * Created: 07/06/2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.infra.rest.provider; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.DateUtils; +import sif3.common.CommonConstants; +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.conversion.MarshalFactory; +import sif3.common.conversion.ModelObjectInfo; +import sif3.common.conversion.UnmarshalFactory; +import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.ExpiredException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; +import sif3.common.exception.UnsupportedQueryException; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.header.HeaderValues.UpdateType; +import sif3.common.interfaces.ChangesSinceProvider; +import sif3.common.interfaces.EventProvider; +import sif3.common.interfaces.FunctionalServiceProvider; +import sif3.common.interfaces.SIFEventIterator; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; +import sif3.common.model.ChangedSinceInfo; +import sif3.common.model.PagingInfo; +import sif3.common.model.RequestMetadata; +import sif3.common.model.ResponseParameters; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFEvent; +import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; +import sif3.common.model.ZoneContextInfo; +import sif3.common.persist.model.SIF3Session; +import sif3.common.persist.service.JobService; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.env.mgr.BrokeredProviderEnvironmentManager; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; +import sif3.infra.rest.client.EventClient; +import sif3.infra.rest.provider.functional.JobEventData; +import sif3.infra.rest.provider.functional.JobEventIterator; +import sif3.infra.rest.provider.functional.JobEventWrapper; + +/** + * This is the main class each specific provider of a given SIF Object type must extends to implement the CRUD operation as defined + * by the SIF3 specification. It is a base implementation of the request connector. It implements some common functionality each provider + * may require. It also ensures that all components of a provider service are wired up correctly within the framework. Please refer to + * the Developer's Guide for some more details about this class. + * + * @author Joerg Huber + */ +public abstract class BaseFunctionalServiceProvider extends CoreProvider implements FunctionalServiceProvider, ChangesSinceProvider, EventProvider +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private final static int MAX_EVENTS = 10; + + private InfraMarshalFactory infraMarshaller = new InfraMarshalFactory(); + private InfraUnmarshalFactory infraUnmarshaller = new InfraUnmarshalFactory(); + + private int maxObjectsInEvent = MAX_EVENTS; + private ArrayList auditZones = null; + + /* + The FunctionalServiceProvider interface defines a couple of method with "Object" as the parameter or return type. This + was done that way, so that the interface doesn't have a dependency on the infrastructure data model. Because the Job is + a infrastructure model we know some more details about it and can narrow down the parameters of these interface methods + to the specific type here. Therefore this class simply re-defines the few FunctionalServiceProvider interface methods + with the applicable infrastructure data model type, so that the implementation of a specific functional service doesn't + need to do type casts or guess work. + */ + + /** + * This method re-maps the FunctionalServiceProvider's method createJob and uses the JobType rather than the Object type as + * parameters. All parameters an behaviour of this method is identical to @see sif3.common.interfaces.FunctionalServiceProvider#createJob(java.lang.Object, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + * + * @param jobData The data of the actual job to be created. It may or may not hold the jobID and the provider may or may not accept it. + * It is up to the implementation to make that decision. The final jobID is returned as part of the returned job. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the job shall be created. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * + * @return The job that is created. It may hold additional data than the one provided. This will be of type JobType as defined + * infrastructure data model. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public abstract JobType createNewJob(JobType jobData, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; + + /** + * Shuts down the sub-class provider. This should release all associated resources with that provider. + */ + public abstract void shutdown(); + + /** + */ + public BaseFunctionalServiceProvider() + { + super(); + setMaxObjectsInEvent(getServiceProperties().getPropertyAsInt("job.event.maxObjects", getServiceName(), MAX_EVENTS)); + auditZones = getAuditZones(); + } + + /* 0 = No events */ + public int getEventFrequency() + { + return getServiceProperties().getPropertyAsInt("job.event.frequency", getServiceName(), CommonConstants.NO_EVENT); + } + + public int getEventDelay() + { + int delay = getServiceProperties().getPropertyAsInt("job.event.startup.delay", getServiceName(), CommonConstants.DEFAULT_EVENT_DELAY); + return (delay < CommonConstants.DEFAULT_EVENT_DELAY) ? CommonConstants.DEFAULT_EVENT_DELAY : delay; + } + + public EventProvider getEventProvider() + { + return BaseFunctionalServiceProvider.this; + } + + /** + * Returns a marshaller applicable for the data model supported for this implementation. + * + * @return See Desc. + */ + public MarshalFactory getMarshaller() + { + return infraMarshaller; + } + + /** + * Returns an unmarshaller applicable for the data model supported for this implementation. + * + * @return See Desc. + */ + public UnmarshalFactory getUnmarshaller() + { + return infraUnmarshaller; + } + + /** + * This is not required for the Job as it is a known model. Simply return an empty object. + * + * @return See Desc. + */ + public ModelObjectInfo getSingleObjectClassInfo() + { + return new ModelObjectInfo(null, null); + } + + public String getServiceName() + { + return getMultiObjectClassInfo().getObjectName(); + } + + public ModelObjectInfo getCollectionObjectClassInfo() + { + return getMultiObjectClassInfo(); + } + + public int getMaxObjectsInEvent() + { + return maxObjectsInEvent; + } + + public void setMaxObjectsInEvent(int maxObjectsInEvent) + { + this.maxObjectsInEvent = maxObjectsInEvent; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#createJob(java.lang.Object, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public Object createJob(Object jobData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException + { + return createNewJob((JobType)jobData, zone, context, metadata, customResponseParams); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#updateJobStatus(java.lang.String, sif3.common.CommonConstants.JobState) + */ + @Override + public void updateJobState(String jobID, JobState newState) throws PersistenceException + { + getJobManager().updateJobState(jobID, newState); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#updatePhaseState(java.lang.String, java.lang.String, sif3.common.CommonConstants.PhaseState) + */ + @Override + public void updatePhaseState(String jobID, String phaseName, PhaseState newState) throws PersistenceException + { + getJobManager().updatePhaseState(jobID, phaseName, newState); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#updateJobStateAndPhaseState(java.lang.String, sif3.common.CommonConstants.JobState, java.lang.String, sif3.common.CommonConstants.PhaseState) + */ + @Override + public void updateJobStateAndPhaseState(String jobID, JobState newJobState, String phaseName, PhaseState newPhaseState) throws PersistenceException + { + getJobManager().updateJobStateAndPhaseState(jobID, newJobState, phaseName, newPhaseState); + } + + /** + * (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#finalise() + */ + public void finalise() + { + // Shut down event timer & task - thread + logger.debug("Finalise in BaseFunctionalProvider for "+getClass().getSimpleName() +" called."); + + // Call finalise on sub-class. + } + + /* (non-Javadoc) + * @see sif3.infra.rest.provider.CoreProvider#finaliseSubClass() + */ + @Override + public void finaliseSubClass() + { + finalise(); + shutdown(); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#getServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + */ + @Override + public HeaderProperties getServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException + { + // For the time being we just call the abstract method getCustomServiceInfo(). + return getCustomServiceInfo(zone, context, pagingInfo, metadata); + } + + /*---------------------------*/ + /*-- Changes Since Methods --*/ + /*---------------------------*/ + /* (non-Javadoc) + * @see sif3.common.interfaces.ChangesSinceProvider#changesSinceSupported() + */ + @Override + public boolean changesSinceSupported() + { + // If job is enabled then changes since is enabled. + return getProviderEnvironment().isJobEnabled(); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.ChangesSinceProvider#getLatestOpaqueMarker(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + */ + @Override + public String getLatestOpaqueMarker(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) + { + // We use the SIF3_JOB_EVENT table for the implementation. This is based around timestamps. + return DateUtils.getISO8601withSecFraction(new Date()); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.ChangesSinceProvider#getChangesSince(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.ChangedSinceInfo, sif3.common.model.RequestMetadata, sif3.common.model.ResponseParameters) + */ + @Override + public Object getChangesSince(SIFZone zone, SIFContext context, PagingInfo pagingInfo, ChangedSinceInfo changedSinceInfo, RequestMetadata metadata, ResponseParameters customResponseParams) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, ExpiredException, SIFException + { + // If it is not supported then we return UnsupportedQueryException + if (changesSinceSupported()) + { + Date changesSince = getChangesSince(changedSinceInfo); + if (changesSince != null) + { + // We only keep changes in the event table for a certain time. The number of days we keep the data is in the + // ProviderEnvironment. If the date of the changes since marker is older than this then we should return ExpiredException. + if (isChangeSinceMarkerExpired(changesSince)) + { + throw new ExpiredException("changesSinceMarker has expired.", "The given changesSinceMarker has expired. Get a new one by calling the appropriate HTTP HEAD method.", "Provider ("+getProviderName()+")"); + } + + return getJobManager().retrieveJobChangesSince(changesSince, getServiceName(), metadata.getFingerprint(), zone, context, pagingInfo); + } + else + { + throw new UnsupportedQueryException("Retrieved invalid Changes Since Marker", "The changes since marker is null.", "Provider ("+getProviderName()+")"); + } + } + else + { + throw new UnsupportedQueryException("Changes Since is not suported","The Functional Services "+getProviderName() + " does not support changes since.", "Provider ("+getProviderName()+")"); + } + } + + /*-------------------------------------*/ + /* Implemented Event Interface Methods */ + /*-------------------------------------*/ + + @Override + public SIFEventIterator getSIFEvents() + { + return new JobEventIterator(getServiceName(), includeConsumerEvents(), getAdapterType(), getServiceProperties()); + } + + @Override + public SIFEvent modifyBeforePublishing(SIFEvent sifEvent, SIFZone zone, SIFContext context, HeaderProperties customHTTPHeaders) + { + return sifEvent; + } + + @Override + public void onEventError(SIFEvent sifEvent, SIFZone zone, SIFContext context) + { + // Events are not published. Report to error log but don't mark them as 'published'. + ArrayList ids = new ArrayList(); + if ((sifEvent != null) && (sifEvent.getSIFObjectList() != null)) + { + for (JobType job : sifEvent.getSIFObjectList().getJob()) + { + ids.add(job.getId()); + } + } + logger.error("Failed to events to zone = "+zone.getUniqueName() + " and Context = " + context.getUniqueName()+ "\n Internal Job Id that failed:\n"+ids); + } + + /*--------------------------------------------------------------------------------------------------------------- + * Section with default behaviour. Some of these methods are based on the provider property file. Some of these + * methods might be overriden by the actual functional service provider class to do some specific tasks that + * are applicable for that class only. + *-------------------------------------------------------------------------------------------------------------*/ + + /* (non-Javadoc) + * @see sif3.common.interfaces.FunctionalServiceProvider#isJobEndState(sif3.common.CommonConstants.JobState) + */ + @Override + public boolean isJobEndState(JobState state) + { + return getProviderEnvironment().getJobEndStates().contains(state); + } + + /*--------------------------------------------------------------------------------------------------------------- + * Methods specific for this class + *-------------------------------------------------------------------------------------------------------------*/ + public void broadcastEvents() + { + int totalRecordsForFingerprintZones = 0; + int failedRecordsForFingerprintZones = 0; + int totalRecordsForAuditZones = 0; + int failedRecordsForAuditZones = 0; + boolean consumerRequestedProperty = getConsumerRequestetProperty(); + boolean includeConsumeRequested = includeConsumerEvents(); + + logger.debug("================================ broadcastEvents() called for provider "+getPrettyName()); + logger.info("Audit Zone List: "+auditZones); + logger.debug("Include Consumer Events: "+includeConsumeRequested); + logger.info("Max objects per SIF Event: "+getMaxObjectsInEvent()); + + BrokeredProviderEnvironmentManager envMgr = getBrokeredEnvironmentManager(); + if (envMgr == null) // not a brokered environment. Cannot sent events! + { + return; // error already logged. + } + + SIF3Session sif3Session = envMgr.getSIF3Session(); + List servicesForProvider = getServicesForProvider(sif3Session, ServiceType.FUNCTIONAL); + + // If there are no services for this provider defined then we don't need to get any events at all. + if ((servicesForProvider == null) || (servicesForProvider.size() == 0)) + { + logger.info("This emvironment does not have any zones and contexts defined for the "+getServiceName() + " service. No events can be sent."); + return; + } + + // lets start sending events.... + try + { + // Let's get the Event Client + String serviceName = getServiceName(); + EventClient evtClient = new EventClient(envMgr, getRequestMediaType(), getResponseMediaType(), serviceName, getMarshaller(), getCompressionEnabled()); + JobEventIterator iterator = (JobEventIterator)getSIFEvents(); + if (iterator != null) + { + while (iterator.hasNext()) + { + // We know it is a "custom" Job Event Iterator. Call specific method and then create necessary + // SIFEvent + JobEventWrapper jobEventWrapper = iterator.getEvents(getMaxObjectsInEvent()); + + // This should not return null since the hasNext() returned true, but just in case we check and exit the loop if it should return null. + // In this case we assume that there is no more data. We also log an error to make the coder aware of the issue. + if (jobEventWrapper != null) + { + logger.debug("Number of "+serviceName+" Job Event Objects: " + jobEventWrapper.getEvents().size()); + + // Do we need to send events to audit zones + if ((auditZones != null) && (auditZones.size() > 0)) + { + // first create SIFEvent which is what we send + SIFEvent events = getEventsFromWrapper(jobEventWrapper, false, false); + for (ZoneContextInfo zoneCtxInfo : auditZones) + { + // Check if provider has rights to publish to given zone and context + if (sif3Session.hasAccess(AccessRight.PROVIDE, AccessType.APPROVED, serviceName, null, zoneCtxInfo.getZone(), zoneCtxInfo.getContext())) + { + failedRecordsForAuditZones = failedRecordsForAuditZones + prepareEventAndSend(evtClient, events, zoneCtxInfo.getZone(), zoneCtxInfo.getContext()); + totalRecordsForAuditZones = totalRecordsForAuditZones + events.getListSize(); + } + else + { + logNoAccessRight(zoneCtxInfo.getZone(), zoneCtxInfo.getContext()); + } + } + } + + // Send to fingerprint: We may need to filter consumerEvents now... + SIFEvent events = getEventsFromWrapper(jobEventWrapper, !consumerRequestedProperty, true); + + // Check if there are actually any events to be sent. Could be none if events are + // consumerRequested only. + if (events.getListSize() > 0) + { + logger.debug("Job Event holds "+events.getListSize()+" Objects for Consumer with Fingerprint = "+events.getFingerprint()); + // Do we have access rights for the requested zone/context (we should but just in case...) + // Check if provider has rights to publish to given zone and context + if (sif3Session.hasAccess(AccessRight.PROVIDE, AccessType.APPROVED, serviceName, null, jobEventWrapper.getZoneContext().getZone(), jobEventWrapper.getZoneContext().getContext())) + { + failedRecordsForFingerprintZones = failedRecordsForFingerprintZones + prepareEventAndSend(evtClient, events, jobEventWrapper.getZoneContext().getZone(), jobEventWrapper.getZoneContext().getContext()); + totalRecordsForFingerprintZones = totalRecordsForFingerprintZones + events.getListSize(); + } + else + { + logNoAccessRight(jobEventWrapper.getZoneContext().getZone(), jobEventWrapper.getZoneContext().getContext()); + } + } + else + { + logger.debug("There are currently no events required to be sent to the consumer with the fingerprint = "+events.getFingerprint()+"."); + } + + // all is sent. We need to mark the events as published. + markEventsAsPublished(jobEventWrapper); + } + else + { + logger.error("iterator.hasNext() has returned true but iterator.getEvent() has retrurned null => no further events are broadcasted."); + break; + } + } + iterator.releaseResources(); + } + else + { + logger.info("getSIFEvents() for provider "+getPrettyName()+" returned null. Currently no events to be sent."); + } + } + catch (Exception ex) + { + logger.error("Failed to retrieve events for provider "+getPrettyName()+": "+ex.getMessage(), ex); + } + + logger.info("Total SIF Event Objects broadcasted to Fingerprint Zones: "+totalRecordsForFingerprintZones); + logger.info("Total SIF Event Objects failed for Fingerprint Zones : "+failedRecordsForFingerprintZones); + if ((auditZones != null) && (auditZones.size() > 0)) + { + logger.info("Total SIF Event Objects broadcasted to Audit Zone(s) : "+totalRecordsForAuditZones); + logger.info("Total SIF Event Objects failed for Audit Zone(s) : "+failedRecordsForAuditZones); + } + logger.debug("================================ Finished broadcastEvents() for provider "+getPrettyName()); + } + + /*---------------------*/ + /*-- Private methods --*/ + /*---------------------*/ + private Date getChangesSince(ChangedSinceInfo changedSinceInfo) + { + try + { + return DateUtils.stringToDate(changedSinceInfo.getChangesSinceMarker(), DateUtils.ISO_8601_SECFRACT); + } + catch (Exception ex) + { + logger.error("Failed to convert changes since marker "+changedSinceInfo.getChangesSinceMarker()+" to date."); + return null; + } + } + + private boolean isChangeSinceMarkerExpired(Date changesSince) + { + Date now = new Date(); + Date oldestValidDate = DateUtils.getDateWithAddedDays(now, -getProviderEnvironment().getJobEventKeepDays()); + + return changesSince.getTime() < oldestValidDate.getTime(); + } + + private SIFEvent getEventsFromWrapper(JobEventWrapper jobEventWrapper, boolean providerEventsOnly, boolean includeFingerprint) + { + SIFEvent events = new SIFEvent(); + events.setSIFObjectList(new JobCollectionType()); + events.setEventAction(jobEventWrapper.getEventAction()); + events.setUpdateType(UpdateType.FULL); + if (includeFingerprint) + { + events.setFingerprint(jobEventWrapper.getFingerprint()); + } + for (JobEventData jobEventData : jobEventWrapper.getEvents()) + { + if (!providerEventsOnly || !jobEventData.isConsumerEvent()) + { + events.getSIFObjectList().getJob().add(jobEventData.getJobData()); + } + } + events.setListSize(events.getSIFObjectList().getJob().size()); + + return events; + } + + /* + * Return the number of failed records. If 0 is return all then the event with its content is sent successfully. + */ + private int prepareEventAndSend(EventClient evtClient, SIFEvent sifEvents, SIFZone zone, SIFContext context) + { + int failedRecords = 0; + HeaderProperties customHTTPHeaders = new HeaderProperties(); + if (!sendEvents(evtClient, sifEvents, zone, context, ServiceType.FUNCTIONAL, customHTTPHeaders)) + { + //Report back to the caller. This should also give the event back to the caller. + onEventError(sifEvents, zone, context); + failedRecords = ((sifEvents != null) ? sifEvents.getListSize() : 0); + } + + return failedRecords; + } + + private void markEventsAsPublished(JobEventWrapper jobEventWrapper) + { + JobService service = new JobService(); + for (JobEventData jobEventData : jobEventWrapper.getEvents()) + { + try + { + service.markJobEventAsPublished(jobEventData.getInternalJobID()); + } + catch (Exception ex) + { + logger.debug("Failed to mark job event with internal ID = " + jobEventData.getInternalJobID() +" as published: "+ex.getMessage(), ex); + } + } + } + + private ArrayList getAuditZones() + { + ArrayList auditZones = null; + String auditZoneStr = getServiceProperties().getPropertyAsString("job.event.auditZones", getServiceName(), null); + if (auditZoneStr != null) + { + auditZones = new ArrayList(); + String[] auditZoneArr = auditZoneStr.split(","); + for (String zoneCtxStr : auditZoneArr) + { + ZoneContextInfo zoneContext = new ZoneContextInfo(); + String[] zoneCtxInfo = zoneCtxStr.split("\\|"); + zoneContext.setZone(new SIFZone(zoneCtxInfo[0].trim())); + if (zoneCtxInfo.length > 1) // we have a context + { + zoneContext.setContext(new SIFContext(zoneCtxInfo[1].trim())); + } + else // default context + { + zoneContext.setContext(new SIFContext(CommonConstants.DEFAULT_CONTEXT_NAME)); + } + auditZones.add(zoneContext); + } + } + return auditZones; + } + + private boolean includeConsumerEvents() + { + boolean includeConsumeRequested = getConsumerRequestetProperty(); + + // If we have auditZone we do need all events for these regardless of the property job.event.includeConsumerRequested + return (auditZones != null) || includeConsumeRequested; + } + + private boolean getConsumerRequestetProperty() + { + return getServiceProperties().getPropertyAsBool("job.event.includeConsumerRequested", getServiceName(), false); + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseProvider.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseProvider.java index a7d2eb73..01354bf4 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseProvider.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/BaseProvider.java @@ -18,16 +18,14 @@ package sif3.infra.rest.provider; -import java.util.Timer; -import java.util.TimerTask; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import au.com.systemic.framework.utils.AdvancedProperties; import sif3.common.CommonConstants; +import sif3.common.conversion.ModelObjectInfo; import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnsupportedQueryException; import sif3.common.header.HeaderProperties; import sif3.common.interfaces.EventProvider; @@ -36,9 +34,6 @@ import sif3.common.model.RequestMetadata; import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; -import sif3.infra.common.env.mgr.ProviderManagerFactory; -import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; -import sif3.infra.common.env.types.ProviderEnvironment; /** * This is the main class each specific provider of a given SIF Object type must extends to implement the CRUD operation as defined @@ -48,40 +43,17 @@ * * @author Joerg Huber */ -public abstract class BaseProvider implements Provider, Runnable +public abstract class BaseProvider extends CoreProvider implements Provider { protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Shuts down the sub-class provider. This should release all associated resources with that provider. + */ + public abstract void shutdown(); - private Timer eventTimer = null; - private TimerTask eventTimerTask = null; - - /** - * Shuts down the sub-class provider. This should release all associated resources with that provider. - */ - public abstract void shutdown(); - - /** - * This method behave like the getServiceInfo() method of the Provider Interface. Please refer to description in that class:

- * - * @see sif3.common.interfaces.Provider#getServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) - * - * @param zone The Zone from which the request is being issued. Can be Null (default Zone) - * @param context The Context for which the objects shall be returned. Can be Null (default Zone) - * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED!). - * @param metadata Metadata relating to the request. Note that most of the properties might be null. - * - * @return Header Properties that shall be returned as HTTP Headers to the caller. Note this can be null or any number of - * custom HTTP headers. Generally one would expect that to be null. - * - * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) - * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the - * message of the exceptions holds some info. - * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. - */ - public abstract HeaderProperties getCustomServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; - - - /** + + /** */ public BaseProvider() { @@ -105,187 +77,66 @@ public BaseProvider() logger.error("Provider "+getProviderName()+" has not implemented the getMultiObjectClassInfo() method properly. It returns null which is not valid."); } } - - /** - * Returns the Provider Configuration in a nice form. Should rarely be required by the concrete implementation of a provider class. - * - * @return See desc. - */ - public ProviderEnvironment getProviderEnvironment() + + public String getServiceName() { - return (ProviderEnvironment)ProviderManagerFactory.getEnvironmentManager().getEnvironmentInfo(); + return getMultiObjectClassInfo().getObjectName(); } - /** - * Utility method to easily retrieve the property file content for a provider. - * - * @return See desc - */ - public AdvancedProperties getServiceProperties() + public ModelObjectInfo getCollectionObjectClassInfo() { - return ProviderManagerFactory.getEnvironmentManager().getServiceProperties(); + return getMultiObjectClassInfo(); } - /** - * Utility method. Mainly used for useful logging messages. - * - * @return Returns the Service Id concatenated with the Provider Name. - */ - public String getPrettyName() + /* 0 = No events */ + public int getEventFrequency() { - return getProviderEnvironment().getAdapterName()+" - " + getProviderName(); + return getServiceProperties().getPropertyAsInt(CommonConstants.FREQ_PROPERTY, getProviderName(), CommonConstants.NO_EVENT); } - /** - * Utility method. Returns the class name of the provider. - * - * @return Returns the Provider Class Name. - */ - public String getProviderName() + public int getEventDelay() { - return getClass().getSimpleName(); + int delay = getServiceProperties().getPropertyAsInt(CommonConstants.EVENT_DELAY_PROPERTY, getProviderName(), CommonConstants.DEFAULT_EVENT_DELAY); + return (delay < CommonConstants.DEFAULT_EVENT_DELAY) ? CommonConstants.DEFAULT_EVENT_DELAY : delay; } - - /** - * This method returns the actual service name (NOT serviceID). The service name is the name that is used in URL paths to identify the - * SIF Object that we are dealing with. The service name is the plural form of the SIF Object name as specified in the SIF3 Spec. - * @return See Desc. - */ - public String getServiceName() + + public EventProvider getEventProvider() { - return getMultiObjectClassInfo().getObjectName(); + return (BaseEventProvider)BaseProvider.this; } - /** * (non-Javadoc) * @see sif3.common.interfaces.Provider#finalise() */ public void finalise() { - // Shut down event timer & task - thread - if (eventTimerTask != null) - { - logger.debug("Shut Down event task for: "+getProviderName()); - eventTimerTask.cancel(); - eventTimerTask = null; - } - if (eventTimer != null) - { - logger.debug("Shut Down event timer for: "+getProviderName()); - eventTimer.cancel(); - eventTimer.purge(); - eventTimer = null; - } - + // Shut down event timer & task - thread + logger.debug("Finalise in BaseProvider for "+getClass().getSimpleName() +" called."); // Call finalise on sub-class. } /* (non-Javadoc) - * @see sif3.common.interfaces.Provider#getServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + * @see sif3.infra.rest.provider.CoreProvider#finaliseSubClass() */ @Override - public HeaderProperties getServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException + public void finaliseSubClass() { - // For the time being we just call the abstract method getCustomServiceInfo(). - return getCustomServiceInfo(zone, context, pagingInfo, metadata); + finalise(); + shutdown(); } - - /*----------------------------------------*/ - /* Implemented Method for Multi-threading */ - /*----------------------------------------*/ - - /** - * This method is all that is needed to run the provider in its own thread. The thread is executed at - * given intervals driven by a property in the adapter's property file. The interval/frequency - * defined in there is used to determine how often this thread is run. - */ + + /* (non-Javadoc) + * @see sif3.common.interfaces.Provider#getServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + */ @Override - public final synchronized void run() + public HeaderProperties getServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException { - String providerName = getProviderName(); - boolean checkEnvType = getServiceProperties().getPropertyAsBool("provider.check.envType", true); - - logger.debug("Start "+providerName+ " thread...."); - - //Only if environment does support events we will start the event manager - if (getProviderEnvironment().getEventsSupported()) - { - // If this is a DIRECT environment then events are not supported, yet. - if ((getProviderEnvironment().getEnvironmentType() == EnvironmentType.DIRECT) && checkEnvType) - { - logger.info("The DIRECT Provider for this framework does NOT support events, yet."); - } - else - { - // Check if the provider implements the events interface. Only then events might be required. - if (EventProvider.class.isAssignableFrom(getClass())) - { - int frequency = getEventFrequency(providerName); - if (frequency != CommonConstants.NO_EVENT) - { - logger.debug("Events supported for this "+providerName+". Start up event thread."); - startupEventManager(providerName, frequency); - } - else - { - logger.info("Events supported for "+providerName+" but currently turned off (frequency=0)"); - } - } - else - { - logger.debug("Events NOT supported for "+providerName+". Provider does not implement EventProvider interface."); - } - } - } - else - { - logger.debug("Environment "+getProviderEnvironment().getEnvironmentName()+ " does NOT support events."); - } - logger.debug(providerName+" started."); + // For the time being we just call the abstract method getCustomServiceInfo(). + return getCustomServiceInfo(zone, context, pagingInfo, metadata); } /*---------------------*/ /*-- Private methods --*/ /*---------------------*/ - - /** - * This method initialises and schedules the event producer task. - */ - //TODO: JH - Consider if I should use Executors.newSingleThreadScheduledExecutor style task/timers here. - private void startupEventManager(String providerName, int frequencyInSec) - { - int period = frequencyInSec * CommonConstants.MILISEC; // repeat every so often (multiply with milliseconds). - int delayInSec = getEventDelay(providerName); - - logger.info(providerName+".startupEventManager: Event Frequency = " + frequencyInSec + " secs; Event Startup Delay = "+delayInSec+" secs."); - if (eventTimerTask == null) // not created started - { - eventTimerTask = new TimerTask() - { - public void run() - { - logger.debug("Start Event Timer Task for "+getMultiObjectClassInfo().getObjectName()+"."); - ((BaseEventProvider)BaseProvider.this).broadcastEvents(); - } - }; - - // Now start scheduling events - logger.debug("Start sending "+getMultiObjectClassInfo().getObjectName()+" events... (Total running threads = "+Thread.activeCount()+")"); - eventTimer = new Timer(true); - eventTimer.scheduleAtFixedRate(eventTimerTask, delayInSec * CommonConstants.MILISEC, period); - } - } - - /* 0 = No events */ - private int getEventFrequency(String providerName) - { - return getServiceProperties().getPropertyAsInt(CommonConstants.FREQ_PROPERTY, providerName, CommonConstants.NO_EVENT); - } - - private int getEventDelay(String providerName) - { - int delay = getServiceProperties().getPropertyAsInt(CommonConstants.EVENT_DELAY_PROPERTY, providerName, CommonConstants.DEFAULT_EVENT_DELAY); - return (delay < CommonConstants.DEFAULT_EVENT_DELAY) ? CommonConstants.DEFAULT_EVENT_DELAY : delay; - } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/CoreProvider.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/CoreProvider.java new file mode 100644 index 00000000..4903ae31 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/CoreProvider.java @@ -0,0 +1,515 @@ +/* + * CoreProvider.java + * Created: 7 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.provider; + +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.AdvancedProperties; +import sif3.common.CommonConstants; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.conversion.ModelObjectInfo; +import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; +import sif3.common.exception.UnsupportedQueryException; +import sif3.common.header.HeaderProperties; +import sif3.common.header.RequestHeaderConstants; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.interfaces.EventProvider; +import sif3.common.model.PagingInfo; +import sif3.common.model.RequestMetadata; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFEvent; +import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; +import sif3.common.persist.model.SIF3Session; +import sif3.common.ws.BaseResponse; +import sif3.infra.common.env.mgr.BrokeredProviderEnvironmentManager; +import sif3.infra.common.env.mgr.ProviderManagerFactory; +import sif3.infra.common.env.types.ProviderEnvironment; +import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; +import sif3.infra.common.interfaces.EnvironmentManager; +import sif3.infra.common.interfaces.ProviderJobManager; +import sif3.infra.rest.client.EventClient; + +/** + * This abstract class forms the basis of all provider style classes. All provider classes must extend this class and then add + * appropriate interfaces or abstract methods to is as required. + * + * @author Joerg Huber + * + */ +public abstract class CoreProvider implements Runnable +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /* properties used for event threads */ + private Timer eventTimer = null; + private TimerTask eventTimerTask = null; + + /** + * Shuts down the sub-class provider. This should release all associated resources with that provider. + */ + public abstract void finaliseSubClass(); + + /** + * This method behave like the getServiceInfo() method of the Provider Interface. Please refer to description in that class:

+ * + * @see sif3.common.interfaces.Provider#getServiceInfo(sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo, sif3.common.model.RequestMetadata) + * + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the objects shall be returned. Can be Null (default Zone) + * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED!). + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * + * @return Header Properties that shall be returned as HTTP Headers to the caller. Note this can be null or any number of + * custom HTTP headers. Generally one would expect that to be null. + * + * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public abstract HeaderProperties getCustomServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; + + /** + * This method returns the actual service name (NOT serviceID). The service name is the name that is used in URL paths to identify the + * SIF Object that we are dealing with. The service name is the plural form of the SIF Object name as specified in the SIF3 Spec. + * + * @return See Desc. + */ + public abstract String getServiceName(); + + /** + * Returns the information for the 'collection-style object'. The returned object holds the name of a 'collection-style object' + * (i.e StudentPersonals) and the physical class this maps to for the data model supported for this implementation. + * + * @return See Desc. + */ + public abstract ModelObjectInfo getCollectionObjectClassInfo(); + + /** + * Returns the frequency in seconds at which events should be published. + * + * @return See desc. + */ + public abstract int getEventFrequency(); + + /** + * Returns the delay in seconds at which various event threads shall be started. + * + * @return See desc. + */ + public abstract int getEventDelay(); + + /** + * Returns the particular event provider class. + * + * @return See desc. + */ + public abstract EventProvider getEventProvider(); + + + /** + */ + public CoreProvider() + { + super(); + } + + /** + * Returns the Provider Configuration in a nice form. Should rarely be required by the concrete implementation of a provider class. + * + * @return See desc. + */ + public ProviderEnvironment getProviderEnvironment() + { + return (ProviderEnvironment)ProviderManagerFactory.getEnvironmentManager().getEnvironmentInfo(); + } + + /** + * Returns the Job Provider Manger for this provider. + * + * @return See desc. + */ + public ProviderJobManager getJobManager() + { + return (ProviderJobManager)ProviderManagerFactory.getEnvironmentManager().getJobManager(); + } + + /** + * Utility method to easily retrieve the property file content for a provider. + * + * @return See desc + */ + public AdvancedProperties getServiceProperties() + { + return ProviderManagerFactory.getEnvironmentManager().getServiceProperties(); + } + + /** + * Utility method to easily retrieve the property file content for a provider. + * + * @return See desc + */ + public AdapterType getAdapterType() + { + return ProviderManagerFactory.getEnvironmentManager().getAdapterType(); + } + + /** + * Utility method. Mainly used for useful logging messages. + * + * @return Returns the Service Id concatenated with the Provider Name. + */ + public String getPrettyName() + { + return getProviderEnvironment().getAdapterName()+" - " + getProviderName(); + } + + /** + * Utility method. Returns the class name of the provider. + * + * @return Returns the Provider Class Name. + */ + public String getProviderName() + { + return getClass().getSimpleName(); + } + + /*------------------------------------------------------------------------------------------------------------------------ + * Start of 'Dynamic' HTTP Header Field override section for Events + * + * The following set of methods are used for a more configurable way how some HTTP header parameters are set. + * By default the following HTTP Header fields are retrieved from the provider's property file and put in corresponding + * HTTP Header Fields of each event: + * + * Property HTTP Header + * ---------------------------------------------------------------- + * adapter.generator.id generatorId + * env.application.key applicationKey + * env.userToken authenticatedUser + * env.mediaType Content-Type, Accept + * adapter.compression.enabled Content-Encoding, Accept-Encoding + * + * Only properties that are not null or empty string will be set in the corresponding HTTP Header. + * + * There are situations where and application may need a more 'dynamic' behaviour where the above values are determined + * at runtime, based on other circumstances and therefore these properties must be retrieved from an other source than the + * providers's property file. In such a case the methods below can be overwritten to make them dynamic and controlled by + * the implementation rather than driven by the provider's property file. If any of the methods below is overwritten then + * the value of the over riding method is set in the corresponding HTTP Header field if the return value of the method + * is not null or an empty string. + *------------------------------------------------------------------------------------------------------------------------*/ + + /** + * This method returns the value of the adapter.generator.id property from the provider's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The adapter.generator.id property from the provider's property file + */ + public String getGeneratorID() + { + return getProviderEnvironment().getGeneratorID(); + } + + /** + * This method returns the value of the env.application.key property from the provider's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.application.key property from the provider's property file + */ + public String getApplicationKey() + { + return getProviderEnvironment().getEnvironmentKey().getApplicationKey(); + } + + /** + * This method returns the value of the env.userToken property from the provider's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.userToken property from the provider's property file + */ + public String getAuthentictedUser() + { + return getProviderEnvironment().getEnvironmentKey().getUserToken(); + } + + /** + * This method returns the value of the env.mediaType property from the provider's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.mediaType property from the provider's property file + */ + public MediaType getRequestMediaType() + { + return getProviderEnvironment().getMediaType(); + } + + /** + * This method returns the value of the env.mediaType property from the provider's property file. If that + * needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The env.mediaType property from the provider's property file + */ + public MediaType getResponseMediaType() + { + return getProviderEnvironment().getMediaType(); + } + + /** + * This method returns the value of the adapter.compression.enabled property from the provider's property file. If + * that needs to be overridden by a specific implementation then the specific sub-class should override this method. + * + * @return The adapter.compression.enabled property from the provider's property file + */ + public boolean getCompressionEnabled() + { + return getProviderEnvironment().getCompressionEnabled(); + } + + /* + * The following properties will be added to the hdrProps. It will use the override methods above. Note that + * the properties related to the media type are set differently and not through the mechanism of this method. + */ + protected void addSIF3OverrideHeaderProperties(HeaderProperties hdrProps) + { + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_GENERATOR_ID, getGeneratorID()); + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_APPLICATION_KEY, getApplicationKey()); + hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTHENTICATED_USER, getAuthentictedUser()); + } + /*------------------------------------------------------------------------------------------------------------------------ + * End of 'Dynamic' HTTP Header Field override section + *-----------------------------------------------------------------------------------------------------------------------*/ + + public void finaliseCoreProvider() + { + logger.debug("Finalise in CoreProvider for "+getClass().getSimpleName() +" called."); + finaliseEventThreads(); + finaliseSubClass(); + } + + /** + * This method is all that is needed to run the provider in its own thread. The thread is executed at + * given intervals driven by a property in the adapter's property file. The interval/frequency + * defined in there is used to determine how often this thread is run. + */ + @Override + public final synchronized void run() + { + String providerName = getProviderName(); + boolean checkEnvType = getServiceProperties().getPropertyAsBool("provider.check.envType", true); + + logger.debug("Start "+providerName+ " thread...."); + + //Only if environment does support events we will start the event manager + if (getProviderEnvironment().getEventsSupported()) + { + // If this is a DIRECT environment then events are not supported, yet. + if ((getProviderEnvironment().getEnvironmentType() == EnvironmentType.DIRECT) && checkEnvType) + { + logger.info("The DIRECT Provider for this framework does NOT support events, yet."); + } + else + { + // Check if the provider implements the events interface. Only then events might be required. + if (EventProvider.class.isAssignableFrom(getClass())) + { + int frequency = getEventFrequency(); + if (frequency != CommonConstants.NO_EVENT) + { + logger.debug("Events supported for this "+providerName+". Start up event thread."); + startupEventManager(providerName, frequency, getEventDelay(), getEventProvider()); + } + else + { + logger.info("Events supported for "+providerName+" but currently turned off (frequency=0)"); + } + } + else + { + logger.debug("Events NOT supported for "+providerName+". Provider does not implement EventProvider interface."); + } + } + } + else + { + logger.debug("Environment "+getProviderEnvironment().getEnvironmentName()+ " does NOT support events."); + } + logger.debug(providerName+" started."); + } + + /*------------------------------------------------------------------------------*/ + /*-- Some utility methods to be used by this class hierarchy only (protected) --*/ + /*------------------------------------------------------------------------------*/ + protected List getServicesForProvider(SIF3Session sif3Session, ServiceType serviceType) + { + if (sif3Session != null) + { + return sif3Session.getServiceInfoForService(getServiceName(), serviceType); + } + + return null; + } + + protected BrokeredProviderEnvironmentManager getBrokeredEnvironmentManager() + { + EnvironmentManager envMgr = ProviderManagerFactory.getEnvironmentManager(); + if (envMgr != null) // we have a proper setup and are initialised. + { + // Note: Currently we only support events for Brokered Environments, so the EnvironmentManager should be of type + // BrokeredProviderEnvironmentManager + if (envMgr instanceof BrokeredProviderEnvironmentManager) + { + return (BrokeredProviderEnvironmentManager)envMgr; + } + else + { + logger.error("Events are only supported for BROKERED environments. This provider is a DIRECT Environment."); + } + } + else + { + logger.error("Environment Manager not initialised. Not connected to an environment. No Environment Manger available."); + } + + // If we get here then we don't have a valid environment manager. + return null; + } + + protected boolean hasAccess(ServiceInfo service, AccessRight accessRight, AccessType accessType) + { + return service.getRights().hasRight(accessRight, accessType); + } + + /** + * If one doesn't want certain events to be published to a given zone then this method needs to be + * overridden. It allows to test for the event and zone and make the appropriate decision if the event + * shall be sent. + * + * @param event The event to be published to the zone. + * @param zone The zone to which the event is published to. + * @param customHTTPHeaders Custom HTTP Headers to be added to the event. + * + * @return TRUE: Event sent successfully. FALSE: Failed to send event. Error must be logged. + */ + protected boolean sendEvents(EventClient evtClient, SIFEvent sifEvents, SIFZone zone, SIFContext context, ServiceType serviceType, HeaderProperties customHTTPHeaders) + { + logger.debug(getPrettyName()+" sending a "+getServiceName()+" event with "+sifEvents.getListSize()+" sif objects."); + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Custom HTTP Headers set by modifyBeforePublishing() method: "+customHTTPHeaders); + } + if (customHTTPHeaders == null) + { + customHTTPHeaders = new HeaderProperties(); + } + + // Add all other HTTP headers to this customHTTPHeader. This ensures that SIF managed HTTP headers will + // override custom HTTP Headers. + addSIF3OverrideHeaderProperties(customHTTPHeaders); + + BaseResponse response = evtClient.sendEvents(sifEvents, zone, context, serviceType, customHTTPHeaders); + if (response.hasError()) + { + logger.error("Failed to send event: "+response.getError()); + return false; + } + else + { + return true; + } + } + catch (Exception ex) + { + logger.error("An error occured sending an event message. See previous error log entries.", ex); + return false; + } + } + + protected void logNoAccessRight(SIFZone zone, SIFContext context) + { + String zoneID = (zone == null) ? "Default" : zone.getId(); + String contextID = (context == null) ? CommonConstants.DEFAULT_CONTEXT_NAME : context.getId(); + + logger.debug("The "+getProviderName()+" does not have the ACL entry PROVIDE = APPROVED for service = " + getServiceName() + " for the Zone = "+ zoneID + " and the Context = " + contextID + ". Events are NOT sent to this zone and context."); + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + + /** + * This method initialises and schedules the event producer task. + */ + // TODO: JH - Consider if I should use Executors.newSingleThreadScheduledExecutor style or even Quartz Job + // task/timers here. + private void startupEventManager(String providerName, int frequencyInSec, int delayInSec, final EventProvider eventProvider) + { + int period = frequencyInSec * CommonConstants.MILISEC; // repeat every so often (multiply with milliseconds). + + logger.info(providerName + ".startupEventManager: Event Frequency = " + frequencyInSec + " secs; Event Startup Delay = " + delayInSec + " secs."); + if (eventTimerTask == null) // not created started + { + eventTimerTask = new TimerTask() + { + public void run() + { + logger.debug("Start Event Timer Task for " + getServiceName() + "."); + eventProvider.broadcastEvents(); + } + }; + + // Now start scheduling events + logger.debug("Start sending " + getServiceName() + " events... (Total running threads = " + Thread.activeCount() + ")"); + eventTimer = new Timer(true); + eventTimer.scheduleAtFixedRate(eventTimerTask, delayInSec * CommonConstants.MILISEC, period); + } + } + + private void finaliseEventThreads() + { + // Shut down event timer & task - thread + if (eventTimerTask != null) + { + logger.debug("Shut Down event task for: "+getProviderName()); + eventTimerTask.cancel(); + eventTimerTask = null; + } + if (eventTimer != null) + { + logger.debug("Shut Down event timer for: "+getProviderName()); + eventTimer.cancel(); + eventTimer.purge(); + eventTimer = null; + } + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/ProviderFactory.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/ProviderFactory.java index 66b2d13a..7008d4df 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/ProviderFactory.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/ProviderFactory.java @@ -30,7 +30,7 @@ import au.com.systemic.framework.utils.AdvancedProperties; import au.com.systemic.framework.utils.StringUtils; import sif3.common.conversion.ModelObjectInfo; -import sif3.common.interfaces.Provider; +import sif3.common.interfaces.EventProvider; /** * This is the provider factory. Each provider deals with a number of objects (i.e. StudentPersonal, SchoolInfo etc). @@ -50,7 +50,7 @@ public class ProviderFactory private static ProviderFactory factory = null; // Active Providers for event publishing. These providers run in the background as an independent thread. - private HashMap eventProviders = new HashMap(); + private HashMap> eventProviders = new HashMap>(); // Known providers that can be instantiated for standard request/response private HashMap providerClasses = new HashMap(); @@ -97,12 +97,12 @@ public static synchronized void shutdown() { if (factory != null) { - for (BaseProvider provider : factory.eventProviders.values()) + for (EventProvider provider : factory.eventProviders.values()) { try { logger.debug("Finalise provider " + provider.getMultiObjectClassInfo().getObjectName() + "..."); - provider.finalise(); + ((CoreProvider)provider).finaliseCoreProvider(); } catch (Exception ex) { @@ -133,14 +133,14 @@ public static ProviderFactory getInstance() } /** - * This method is intended to be used to get a new instance of an object provider class to be used for request/response methods. + * This method is intended to be used to get a new instance of an provider class to be used for request/response methods. * If provider doesn't exist for the given objectInfo then null is returned. * * @param objectInfo must have at least the ObjectName property set otherwise null is returned and an error is logged. * * @return See desc. */ - public Provider getProvider(ModelObjectInfo objectInfo) + public Object getProvider(ModelObjectInfo objectInfo) { if ((objectInfo != null) && (StringUtils.notEmpty(objectInfo.getObjectName()))) { @@ -149,7 +149,7 @@ public Provider getProvider(ModelObjectInfo objectInfo) { try { - return (BaseProvider) providerClassInfo.getClassInstance(null); + return providerClassInfo.getClassInstance(null); } catch (Exception ex) { @@ -196,17 +196,21 @@ private void initialiseProviders(AdvancedProperties adapterProps) Object classObj = providerClassInfo.getClassInstance(null); // Set properties and add it to correct structure - if (classObj instanceof BaseProvider) + if (classObj instanceof CoreProvider) { - BaseProvider provider = (BaseProvider) classObj; - ModelObjectInfo objectInfo = provider.getMultiObjectClassInfo(); + CoreProvider provider = (CoreProvider) classObj; + ModelObjectInfo objectInfo = provider.getCollectionObjectClassInfo(); if ((objectInfo != null) && (StringUtils.notEmpty(objectInfo.getObjectName()))) { // First add it to the standard request/response hashmap providerClasses.put(objectInfo, providerClassInfo); - // Add it to hasmap for background threads - eventProviders.put(objectInfo, provider); + // So far only Object Services (BaseProvider) support events. + if (classObj instanceof EventProvider) + { + // Add it to hasmap for background threads + eventProviders.put(objectInfo, (EventProvider)provider); + } } else { @@ -236,14 +240,15 @@ private void startProviders(AdvancedProperties adapterProps) throws Exception logger.debug("Start up delay between providers is: "+delay+" seconds"); int i = 0; - for (BaseProvider provider : eventProviders.values()) + for (EventProvider provider : eventProviders.values()) { // Create thread in thread pool providerService = Executors.newSingleThreadScheduledExecutor(); // Ensure there is 10 seconds between the start of each publisher so that they don't hammer // the system at the same time during startup. Startup thread on thread pool. - providerService.schedule(provider, i*delay, TimeUnit.SECONDS); +// ScheduledFuture future = providerService.schedule((CoreProvider)provider, i*delay, TimeUnit.SECONDS); + providerService.schedule((CoreProvider)provider, i*delay, TimeUnit.SECONDS); i++; } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventData.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventData.java new file mode 100644 index 00000000..c56684c1 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventData.java @@ -0,0 +1,73 @@ +/* + * JobEventData.java + * Created: 11 Sep 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.provider.functional; + +import java.io.Serializable; + +import sif3.infra.common.model.JobType; + +/** + * This is a helper POJO to manage the publishing of Job Events. + * + * @author Joerg Huber + */ +public class JobEventData implements Serializable +{ + private static final long serialVersionUID = -1008848372047662458L; + + private long internalJobID = -1; // needed for updating job event table + private boolean consumerEvent = true; // required to filter jobs for audit and non audit zones + private JobType jobData = null; // actual job SIF Data Object. + + public long getInternalJobID() + { + return internalJobID; + } + + public void setInternalJobID(long internalJobID) + { + this.internalJobID = internalJobID; + } + + public boolean isConsumerEvent() + { + return consumerEvent; + } + + public void setConsumerEvent(boolean consumerEvent) + { + this.consumerEvent = consumerEvent; + } + + public JobType getJobData() + { + return jobData; + } + + public void setJobData(JobType jobData) + { + this.jobData = jobData; + } + + @Override + public String toString() + { + return "JobEventData [internalJobID=" + internalJobID + ", consumerEvent=" + consumerEvent + + ", jobData=" + jobData + "]"; + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventIterator.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventIterator.java new file mode 100644 index 00000000..3fd31bb7 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventIterator.java @@ -0,0 +1,213 @@ +/* + * JobEventIterator.java + * Created: 4 Sep 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.provider.functional; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.AdvancedProperties; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.interfaces.SIFEventIterator; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFEvent; +import sif3.common.model.SIFZone; +import sif3.common.model.ZoneContextInfo; +import sif3.common.persist.model.SIF3JobEvent; +import sif3.common.persist.service.JobService; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; + +/** + * This method is the implementation for the event iterator for functional service jobs. Because the framework + * maintains events internally and the event object is known for functional services this can fully be implemented + * by this framework and doesn't have to be done by individual provider classes as with object services. + * + * @author Joerg Huber + */ +public class JobEventIterator implements SIFEventIterator +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private InfraUnmarshalFactory infraUnmarshaller = new InfraUnmarshalFactory(); + + private int currentPos = 0; + private List jobEvents = new ArrayList(); + + public JobEventIterator(String serviceName, boolean includeConsumeRequested, AdapterType adapterType, AdvancedProperties serviceProperties) + { + currentPos = 0; + try + { + JobService service = new JobService(); + + jobEvents = service.retrieveJobEvents(serviceName, adapterType, includeConsumeRequested); + } + catch (Exception ex) + { + logger.error("Failed to retrieve Job Events for Functional Service "+serviceName+": "+ex.getMessage(), ex); + jobEvents = new ArrayList(); + } + } + + /* + * We do not implement this method! For Jobs we need to do some more sophistication and since the publishing + * of Job Events is under full control of the BaseEventProvider we can implement another method that is + * specifically geared towards the way Job Event publishing may work. The core issue is that Job events are + * published to the 'fingerprint' consumer only but also to some generic auditZones. Later the published jobs + * need to be marked as published. So a job event may need to be published to a few zones and a different set of + * events may need to be published to each zone. The 'fingerprint' belongs to a specific zone and indicates + * the owner of the job. Most likely the consumerCreated events do not need to be published to that zone + * where as provider created events do. In Audit Zones all events need to be published. To avoid too many + * events to be published to the 'fingerprint' zone we need a little bit more info in the getNextEvent() + * method than just a list of SIF Job Objects. + * + * (non-Javadoc) + * @see sif3.common.interfaces.SIFEventIterator#getNextEvents(int) + */ + @Override + public SIFEvent getNextEvents(int maxListSize) + { + return null; + } + + /** + * This method returns the next set of events. The max size of the list is given by the maxListSize parameter. + * Note the returned list holds events of the same event type and the same zone, context and fingerprint. + * The BaseFunctionalServiceProvider must use this method to get the next set of events rather than the + * getNextEvents(int maxListSize) method above. If null is returned by this method then it must be assumed + * that there are no more events available. + * + * @param maxListSize + * + * @return See desc. + */ + public JobEventWrapper getEvents(int maxListSize) + { + JobEventWrapper events = null; + if (hasNext()) + { + events = new JobEventWrapper(); + events.setEvents(new ArrayList()); + while ((events.getEvents().size() < maxListSize) && hasNext()) + { + SIF3JobEvent jobEvent = jobEvents.get(currentPos); + currentPos++; + + try + { + if (events.getEvents().size() == 0) // first record => initialise wrapper + { + events.setEventAction(mapEventType(jobEvent.getEventType())); + events.setFingerprint(jobEvent.getFingerprint()); + events.setZoneContext(new ZoneContextInfo(new SIFZone(jobEvent.getZoneID()), new SIFContext(jobEvent.getContextID()))); + + addEventToWrapper(events, jobEvent); + } + else + { + EventAction eventAction = mapEventType(jobEvent.getEventType()); + if ((events.getEventAction() == eventAction) && + events.getFingerprint().equals(jobEvent.getFingerprint()) && + events.getZoneContext().getZone().getId().equals(jobEvent.getZoneID()) && + events.getZoneContext().getContext().getId().equals(jobEvent.getContextID())) + { + addEventToWrapper(events, jobEvent); + } + else // We are done for this set. Roll back counter as this event isn't processed yet + { + currentPos--; + break; // and stop.... + } + } + } + catch (Exception ex) // most likely failed to unmarshal event. Should not really happen! + { + logger.error("Failed to extract Jobe Event Data for Job Event ID = "+jobEvent.getInternalID()+": "+ex.getMessage()+". Ignore this event.", ex); + } + } + } + + return events; + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.SIFEventIterator#hasNext() + */ + @Override + public boolean hasNext() + { + return (currentPos < jobEvents.size()); + } + + /* (non-Javadoc) + * @see sif3.common.interfaces.SIFEventIterator#releaseResources() + */ + @Override + public void releaseResources() + { + jobEvents = new ArrayList(); + currentPos = 0; + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + private EventAction mapEventType(String jobDBEventType) + { + if ("U".equalsIgnoreCase(jobDBEventType)) + { + return EventAction.UPDATE; + } + if ("C".equalsIgnoreCase(jobDBEventType)) + { + return EventAction.CREATE; + } + if ("D".equalsIgnoreCase(jobDBEventType)) + { + return EventAction.DELETE; + } + + return null; + } + + private void addEventToWrapper(JobEventWrapper events, SIF3JobEvent jobEvent) throws Exception + { + JobType job = null; + JobEventData event = new JobEventData(); + event.setConsumerEvent(jobEvent.isConsumerRequested()); + event.setInternalJobID(jobEvent.getInternalID()); + + // If it is a delete event then we simply create a empty JobType with only the refID populated. + if (events.getEventAction() == EventAction.DELETE) + { + job = new JobType(); + job.setId(jobEvent.getJobID()); + } + else + { + job = (JobType)infraUnmarshaller.unmarshalFromXML(jobEvent.getJobXML(), JobType.class); + } + event.setJobData(job); + + events.getEvents().add(event); + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventWrapper.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventWrapper.java new file mode 100644 index 00000000..6798ed9e --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/provider/functional/JobEventWrapper.java @@ -0,0 +1,85 @@ +/* + * JobEventWrapper.java + * Created: 11 Sep 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.provider.functional; + +import java.io.Serializable; +import java.util.ArrayList; + +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.ZoneContextInfo; + +/** + * This is a helper POJO to manage the publishing of Job Events. + * + * @author Joerg Huber + */ +public class JobEventWrapper implements Serializable +{ + private static final long serialVersionUID = -5053804097493083125L; + + private EventAction eventAction; + private String fingerprint; + private ZoneContextInfo zoneContext; + private ArrayList events; + + public EventAction getEventAction() + { + return eventAction; + } + + public void setEventAction(EventAction eventAction) + { + this.eventAction = eventAction; + } + + public String getFingerprint() + { + return fingerprint; + } + + public void setFingerprint(String fingerprint) + { + this.fingerprint = fingerprint; + } + + public ZoneContextInfo getZoneContext() + { + return zoneContext; + } + + public void setZoneContext(ZoneContextInfo zoneContext) + { + this.zoneContext = zoneContext; + } + + public ArrayList getEvents() + { + return events; + } + public void setEvents(ArrayList events) + { + this.events = events; + } + + @Override + public String toString() + { + return "JobEventWrapper [eventAction=" + eventAction + ", fingerprint=" + fingerprint + + ", zoneContext=" + zoneContext + ", events=" + events + "]"; + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/FunctionalServiceHouseKeeping.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/FunctionalServiceHouseKeeping.java new file mode 100644 index 00000000..b92b8f4f --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/FunctionalServiceHouseKeeping.java @@ -0,0 +1,150 @@ +/* + * JobHouseKeeping.java + * Created: 21 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.quarz; + +import java.util.Date; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.TriggerBuilder; +import org.quartz.impl.StdSchedulerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.DateUtils; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.persist.service.JobService; +import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; +import sif3.infra.common.env.types.ProviderEnvironment; + +/** + * @author Joerg Huber + * + */ +public class FunctionalServiceHouseKeeping extends QuartzBase implements Job +{ + private static final long serialVersionUID = 9162279022523606724L; + + public static final String JOB_NAME = "FunctionalServiceHouseKeeping"; + public static final String TRIGGER_NAME = "FunctionalServiceHouseKeeping-Trigger"; + public static final String ENV_PARAM_NAME = "ENV_DATA"; + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private Scheduler scheduler = null; + + /** + * This method schedules the FunctionalServiceHouseKeeping Quartz Job based on the data given in the 'env' parameter. + * + * @param env Holds data required to schedule and execute the Quartz job. + */ + public boolean start(ProviderEnvironment env) + { + try + { + JobDetail job = JobBuilder.newJob(FunctionalServiceHouseKeeping.class).withIdentity(JOB_NAME, QuartzBase.PROVIDER_GROUP).build(); + + // Store the provider environment setup in the job. + job.getJobDataMap().put(ENV_PARAM_NAME, env); + + // Build the Cron Trigger + CronTrigger trigger = TriggerBuilder.newTrigger() + .withIdentity(TRIGGER_NAME, QuartzBase.PROVIDER_GROUP) + .withSchedule(CronScheduleBuilder.cronSchedule(env.getJobHousekeepingCron())) + .build(); + + setScheduler(new StdSchedulerFactory().getScheduler()); + getScheduler().start(); + + // Pass the Quartz Job with its parameters and the trigger to the Quartz Scheduler. + getScheduler().scheduleJob(job, trigger); + return true; + } + catch (Exception ex) + { + logger.error("Failed to schedule FunctionalServiceHouseKeeping Job: "+ex.getMessage()); + return false; + } + } + + public void shutdown() + { + logger.debug("Shutting down JobHouseKeeping..."); + try + { + if (scheduler != null) + { + scheduler.shutdown(true); + } + } + catch (Exception ex) + { + logger.error("Failed to properly shut down the FunctionalServiceHouseKeeping Job: "+ex.getMessage()); + } + } + + /* (non-Javadoc) + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + @Override + public void execute(JobExecutionContext jobctx) throws JobExecutionException + { + JobKey jobKey = jobctx.getJobDetail().getKey(); + logger.debug("JobHouseKeeping says: " + jobKey + " executing at " + new Date()+". Number of Threads: "+ Thread.activeCount()); + ProviderEnvironment env = (ProviderEnvironment)jobctx.getJobDetail().getJobDataMap().get(ENV_PARAM_NAME); + + try + { + JobService svc = new JobService(); + + // Determine the date for job events to remove + Date now = new Date(); + Date oldEventDate = DateUtils.getDateWithAddedDays(now, -env.getJobEventKeepDays()); + svc.removeJobEvents(oldEventDate, getAdapterType(env)); + + // Determine the date for "stale" jobs. + Date staleJobDate = DateUtils.getDateWithAddedDays(now, -env.getJobKeepDays()); + svc.removeExpiredAndStaleJobs(staleJobDate, getAdapterType(env)); + } + catch (Exception ex) + { + logger.error("Failed to run all Functional Services Housekeeping jobs. Error reported: "+ex.getMessage(), ex); + } + } + + private Scheduler getScheduler() + { + return scheduler; + } + + private void setScheduler(Scheduler scheduler) + { + this.scheduler = scheduler; + } + + private AdapterType getAdapterType(ProviderEnvironment env) + { + return env.getEnvironmentType() == EnvironmentType.BROKERED ? AdapterType.PROVIDER : AdapterType.ENVIRONMENT_PROVIDER; + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/QuartzBase.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/QuartzBase.java new file mode 100644 index 00000000..ffb000ad --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/quarz/QuartzBase.java @@ -0,0 +1,36 @@ +/* + * QuartzBase.java + * Created: 26 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.quarz; + +import java.io.Serializable; + +/** + * Base class with useful methods and constants to be used in this package for scheduling tasks using Quartz. + * @author Joerg Huber + * + */ +public class QuartzBase implements Serializable +{ + private static final long serialVersionUID = -4629650291775704231L; + + /* Group to be used with Provider Quartz Jobs */ + public static final String PROVIDER_GROUP = "ProviderQuartzGroup"; + + /* Group to be used with Consumer Quartz Jobs */ + public static final String CONSUMER_GROUP = "ConsumerQuartzGroup"; +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalConsumerQueue.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalConsumerQueue.java index c8329a7b..f324478e 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalConsumerQueue.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalConsumerQueue.java @@ -19,11 +19,13 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants; import sif3.infra.rest.queue.types.QueueMessage; @@ -53,6 +55,8 @@ public class LocalConsumerQueue /* Properties used in future development once persistence will be implemented. */ private String localQueueID; + private boolean shutdownFlag = false; + @SuppressWarnings("unused") private String workingDir; @@ -102,7 +106,7 @@ public void blockingPush(QueueMessage message) } catch (Exception ex) { - logger.error("Failed to push the 'message' on to the LocalConsumerQueue: "+ex.getMessage(),ex); + logger.error("Failed to push the 'message' on to the "+getConsumerName()+": "+ex.getMessage(),ex); } } @@ -117,16 +121,22 @@ public QueueMessage blockingPull() { try { - return queue.take(); + return blockingRead(); } catch (Exception ex) { - logger.error("Failed to pull a message off the the LocalConsumerQueue: "+ex.getMessage(),ex); + logger.error("Failed to pull a message off the "+getConsumerName()+": "+ex.getMessage(),ex); return null; } } - /* (non-Javadoc) + public void shutdown() + { + logger.debug("Shutdown message received for "+getConsumerName()); + shutdownFlag = true; + } + + /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override @@ -134,4 +144,50 @@ public String toString() { return "LocalConsumerQueue [localQueueID=" + localQueueID + "]"; } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + + /* + * This method will read from the queue for a max of CommonConstants.MAX_SLEEP_MILLISEC before it will check if a shutdown + * was requested. If it is it will return null otherwise it will continue to read until a proper message is available form the + * queue. If a message is available it will be returned. + * This method helps in the proper shutdown of consumer and local queues during the shutdown process. Without that approach + * this class which runs in its own thread never shuts down properly. + */ + private QueueMessage blockingRead() + { + while (!shutdownFlag) + { + try + { + QueueMessage message = queue.poll(CommonConstants.MAX_SLEEP_MILLISEC, TimeUnit.MILLISECONDS); + + if (message != null) // we have received a message! + { + return message; + } + + if (!shutdownFlag) + { + logger.debug("No message received for "+getConsumerName()+". Go back to read again ..."); + } + } + catch (Exception ex) + { + logger.error("Failed to pull a message off the "+getConsumerName()+": "+ex.getMessage(),ex); + return null; + } + } + + // If we get here then the shutdown has been called! + logger.debug("Stop reading from "+getConsumerName()+". Shutdown Flag = "+shutdownFlag); + return null; + } + + private String getConsumerName() + { + return "LocalConsumerQueue: "+getLocalQueueID() + "("+Thread.currentThread().getId()+")"; + } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalMessageConsumer.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalMessageConsumer.java index 794b08b2..ccdcf9a6 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalMessageConsumer.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/LocalMessageConsumer.java @@ -2,7 +2,7 @@ * LocalConsumerQueue.java * Created: 06/05/2014 * - * Copyright 2014 Systemic Pty Ltd + * Copyright 2014-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,23 +21,29 @@ import java.util.List; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sun.jersey.api.client.ClientResponse.Status; - +import au.com.systemic.framework.utils.StringUtils; import sif3.common.exception.UnmarshalException; import sif3.common.exception.UnsupportedMediaTypeExcpetion; -import sif3.common.interfaces.Consumer; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.interfaces.DelayedConsumer; import sif3.common.interfaces.EventConsumer; +import sif3.common.interfaces.MinimalConsumer; import sif3.common.model.PagingInfo; import sif3.common.model.QueryCriteria; import sif3.common.model.SIFEvent; import sif3.common.model.ZoneContextInfo; import sif3.common.model.delayed.DelayedResponseReceipt; +import sif3.common.model.job.PhaseInfo; import sif3.common.ws.ErrorDetails; +import sif3.common.ws.job.PhaseDataResponse; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.rest.consumer.AbstractFunctionalServiceConsumer; import sif3.infra.rest.mapper.InfraDataModelMapper; import sif3.infra.rest.queue.types.DelayedBaseInfo; import sif3.infra.rest.queue.types.ErrorInfo; @@ -60,22 +66,23 @@ public class LocalMessageConsumer implements Runnable private LocalConsumerQueue localQueue; private String consumerID; - private Consumer consumer; + private MinimalConsumer minimalConsumer; private InfraDataModelMapper infraMapper = new InfraDataModelMapper(); - + private boolean shutdownFlag = false; + /** * This method initialises a Consumer to be able to receive and process messages from the local event queue. The 'consumer' parameter is * required by the local consumer as it invokes the consumer's methods for processing events or delayed responses. * * @param localQueue The local queue on which this consumer will be listening on. * @param consumerID A name of the consumer. Mainly needed for nice debug and error reporting. - * @param consumer An instance of consumer that will process the message. + * @param minimalConsumer An instance of consumer that will process the message. */ - public LocalMessageConsumer(LocalConsumerQueue localQueue, String consumerID, Consumer consumer) + public LocalMessageConsumer(LocalConsumerQueue localQueue, String consumerID, MinimalConsumer minimalConsumer) { this.localQueue = localQueue; this.consumerID = consumerID; - this.consumer = consumer; + this.minimalConsumer = minimalConsumer; } /** @@ -86,9 +93,23 @@ public LocalMessageConsumer(LocalConsumerQueue localQueue, String consumerID, Co //@Override public void run() { - consume(); + try + { + consume(); + } + catch (InterruptedException ex) + { + logger.error("Thread interupted - application shutting down...", ex); + } } + public void shutdown() + { + logger.debug("Shutdown message received for LocalConsumerConsumer: "+consumerID); + localQueue.shutdown(); + shutdownFlag = true; + } + /*-----------------*/ /* Private methods */ /*-----------------*/ @@ -96,38 +117,57 @@ public void run() * This method will run in an infinite loop and try to retrieve messages from the local queue. Once * a message is retrieved it will determine if it will be sent to appropriate consumer thread for it to be processed. */ - private void consume() + private void consume() throws InterruptedException { - while (true) + while (!shutdownFlag) { - QueueMessage message = localQueue.blockingPull(); - if (message != null) - { - // Check what type of message it is. - switch (message.getMessageType()) - { - case EVENT: - { - processEvent((EventInfo)message); - break; - } - case RESPONSE: + try + { + QueueMessage message = localQueue.blockingPull(); + if (message != null) + { + // Check what type of message it is. + switch (message.getMessageType()) { - processResponse((ResponseInfo)message); - break; - } - case ERROR: - { - processError((ErrorInfo)message); - break; + case EVENT: + { + processEvent((EventInfo)message); + break; + } + case RESPONSE: + { + processResponse((ResponseInfo)message); + break; + } + case ERROR: + { + processError((ErrorInfo)message); + break; + } } + } + else + { + if (!shutdownFlag) + { + logger.error("LocalMessage Consumer " + consumerID + " has encountered a problem receiving an message from its local consumer queue."); + } + } + } + catch (Exception ex) + { + if ((ex != null) && (ex instanceof InterruptedException)) + { + throw (InterruptedException) ex; } - } - else - { - logger.error(consumerID + " has encountered a problem receiving an message from its local consumer queue."); - } + else + { + // Error should already have been logged. Just wait and try again + logger.error("LocalMessage Consumer " + consumerID + " has encountered a problem receiving an message from its local consumer queue.", ex); + } + } } + logger.debug("LocalMessage Consumer " + consumerID + " stopped reading messages. Shutdown flag: "+shutdownFlag); } /*---------------------*/ @@ -137,8 +177,16 @@ private void consume() private void processEvent(EventInfo eventInfo) { logger.debug(consumerID + " has receive an event from its local consumer queue ID: " + eventInfo.getMessageQueueReaderID()); - EventConsumer eventConsumer = (EventConsumer) consumer; - Object eventPayload = makeDataModelObject(consumer, eventInfo.getPayload(), eventInfo.getMediaType()); + EventConsumer eventConsumer = (EventConsumer)minimalConsumer; + Object eventPayload = null; + if (eventInfo.getServiceType() == ServiceType.FUNCTIONAL) + { + eventPayload = makeFunctionalServiceObject(eventInfo.getPayload(), eventInfo.getMediaType(), false, true); + } + else + { + eventPayload = makeDataModelObject(minimalConsumer, eventInfo.getPayload(), eventInfo.getMediaType()); + } if (eventPayload instanceof ErrorDetails) { logger.error("Failed to create and send actual event to event consumer (" + eventConsumer.getClass().getSimpleName() + "): " + eventPayload); @@ -169,7 +217,7 @@ private void processResponse(ResponseInfo responseInfo) logger.debug(responseInfo.toString()); } - DelayedConsumer delayedConsumer = (DelayedConsumer)consumer; + DelayedConsumer delayedConsumer = (DelayedConsumer)minimalConsumer; DelayedResponseReceipt delayedReceipt = extractDelayedReceiptInfo(responseInfo); if (responseInfo.getResponseAction() == null) // this is not good and something is terribly wrong. @@ -182,27 +230,91 @@ private void processResponse(ResponseInfo responseInfo) return; } + boolean isPhaseObject = StringUtils.notEmpty(responseInfo.getPhaseName()); + // If we get here we know what to do with the payload. switch (responseInfo.getResponseAction()) { case CREATE: { - delayedConsumer.onCreateMany(infraMapper.toStatusListFromSIFCreateString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); - break; + if (responseInfo.getServiceType() == ServiceType.FUNCTIONAL) + { + if (!isPhaseObject) + { + delayedConsumer.onCreateMany(infraMapper.toStatusListFromSIFCreateString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + } + else + { + AbstractFunctionalServiceConsumer fsc = (AbstractFunctionalServiceConsumer)minimalConsumer; + fsc.processDelayedPhaseCreate(new PhaseInfo(responseInfo.getResourceID(), responseInfo.getPhaseName()), + new PhaseDataResponse(responseInfo.getPayload(), responseInfo.getMediaType(), Status.OK), + delayedReceipt); + } + } + else + { + delayedConsumer.onCreateMany(infraMapper.toStatusListFromSIFCreateString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + } + break; } case DELETE: { - delayedConsumer.onDeleteMany(infraMapper.toStatusListFromSIFDeleteString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + if (responseInfo.getServiceType() == ServiceType.FUNCTIONAL) + { + if (!isPhaseObject) + { + delayedConsumer.onDeleteMany(infraMapper.toStatusListFromSIFDeleteString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + + } + else + { + AbstractFunctionalServiceConsumer fsc = (AbstractFunctionalServiceConsumer)minimalConsumer; + fsc.processDelayedPhaseDelete(new PhaseInfo(responseInfo.getResourceID(), responseInfo.getPhaseName()), + new PhaseDataResponse(responseInfo.getPayload(), responseInfo.getMediaType(), Status.OK), + delayedReceipt); + } + } + else + { + delayedConsumer.onDeleteMany(infraMapper.toStatusListFromSIFDeleteString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + } break; } case UPDATE: { - delayedConsumer.onUpdateMany(infraMapper.toStatusListFromSIFUpdateString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + if (responseInfo.getServiceType() == ServiceType.FUNCTIONAL) + { + if (isPhaseObject) + { + AbstractFunctionalServiceConsumer fsc = (AbstractFunctionalServiceConsumer)minimalConsumer; + fsc.processDelayedPhaseUpdate(new PhaseInfo(responseInfo.getResourceID(), responseInfo.getPhaseName()), + new PhaseDataResponse(responseInfo.getPayload(), responseInfo.getMediaType(), Status.OK), + delayedReceipt); + } + else + { + logger.error("Received a DELAYED UPDATE Response for Jobs of Functional Services. This is an invalid operation. Data ignored."); + } + } + else + { + delayedConsumer.onUpdateMany(infraMapper.toStatusListFromSIFUpdateString(responseInfo.getPayload(), responseInfo.getMediaType()), delayedReceipt); + } break; } case QUERY: { - Object payloadObject = makeDataModelObject(consumer, responseInfo.getPayload(), responseInfo.getMediaType()); + Object payloadObject = null; + + if (responseInfo.getServiceType() == ServiceType.FUNCTIONAL) + { + payloadObject = makeFunctionalServiceObject(responseInfo.getPayload(), responseInfo.getMediaType(), isPhaseObject, false); + } + else + { + payloadObject = makeDataModelObject(minimalConsumer, responseInfo.getPayload(), responseInfo.getMediaType()); + } + if (payloadObject instanceof ErrorDetails) { // Something is not good at all. We send the error to the consumer. @@ -224,6 +336,21 @@ private void processResponse(ResponseInfo responseInfo) delayedConsumer.onServicePath(payloadObject, new QueryCriteria(responseInfo.getUrlService()), paging, delayedReceipt); break; } + case FUNCTIONAL: + { + if (!isPhaseObject) + { + delayedConsumer.onQuery(payloadObject, paging, delayedReceipt); + } + else + { + AbstractFunctionalServiceConsumer fsc = (AbstractFunctionalServiceConsumer)minimalConsumer; + fsc.processDelayedPhaseQuery(new PhaseInfo(responseInfo.getResourceID(), responseInfo.getPhaseName()), + new PhaseDataResponse(responseInfo.getPayload(), responseInfo.getMediaType(), Status.OK), + paging, delayedReceipt); + } + break; + } default: { logger.error("Received a Query Response for a Servic Type ("+responseInfo.getServiceType()+") that is not yet supported with this framework. Ignore message:\n"+responseInfo); @@ -241,8 +368,16 @@ private void processResponse(ResponseInfo responseInfo) private void processError(ErrorInfo errorInfo) { logger.debug(consumerID + " has receive a DELAYED ERROR from its local consumer queue ID: " + errorInfo.getMessageQueueReaderID()); - DelayedConsumer delayedConsumer = (DelayedConsumer)consumer; - delayedConsumer.onError(infraMapper.toErrorFromSIFErrorString(errorInfo.getPayload(), errorInfo.getMediaType(), new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not extract error from payload.", "Payload:\n"+errorInfo.getPayload())), extractDelayedReceiptInfo(errorInfo)); + DelayedConsumer delayedConsumer = (DelayedConsumer)minimalConsumer; + + // If an error was returned then it is already unmarshalled and set in the errorInfo object. If it is null though + // then we set a dummy value... + ErrorDetails errorDetails = errorInfo.getErrorDetail(); + if (errorDetails == null) // create dummy error as it seems something is wrong + { + errorDetails = new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not extract error from payload."); + } + delayedConsumer.onError(errorDetails, extractDelayedReceiptInfo(errorInfo)); } /* @@ -250,11 +385,11 @@ private void processError(ErrorInfo errorInfo) * returned object of this method is not a Datamodel object but an ErrorDetails object. The caller of this method MUST * check if the returned object is of type ErrorDetails first before doing anything with it. */ - private Object makeDataModelObject(Consumer consumer, String payload, MediaType mediaType) + private Object makeDataModelObject(MinimalConsumer miniConsumer, String payload, MediaType mediaType) { try { - return consumer.getUnmarshaller().unmarshal(payload, consumer.getMultiObjectClassInfo().getObjectType(), mediaType); + return miniConsumer.getUnmarshaller().unmarshal(payload, miniConsumer.getMultiObjectClassInfo().getObjectType(), mediaType); } catch (UnmarshalException ex) { @@ -269,6 +404,39 @@ private Object makeDataModelObject(Consumer consumer, String payload, MediaType return new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal Could not unmarshal Datamodel payload into SIF Object: "+ex.getMessage()+". See error description for payload details.", payload); } } + + /* + * Will only be called for Job Queries. So we know that it must be a JobCollection object. The other usage is from Phase Operations. + * In this case we know nothing about the data model and therefore we return the raw data (string). + */ + private Object makeFunctionalServiceObject(String payload, MediaType mediaType, boolean isPhaseObject, boolean isEvent) + { + InfraUnmarshalFactory infraUnmarshaller = new InfraUnmarshalFactory(); + if (isEvent || !isPhaseObject) // The object is a Job Collection object + { + try + { + return infraUnmarshaller.unmarshal(payload, JobCollectionType.class, mediaType); + } + catch (UnmarshalException ex) + { + return new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not unmarshal Job Collection payload into SIF Object: "+ex.getMessage()+". See error description for payload details.", payload, "Consumer"); + } + catch (UnsupportedMediaTypeExcpetion ex) + { + return new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal Job Collection payload into SIF Object (unsupported media type): "+ex.getMessage()+". See error description for payload details.", payload, "Consumer"); + } + catch (Exception ex) + { + return new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal Could not unmarshal Job Collection payload into SIF Object: "+ex.getMessage()+". See error description for payload details.", payload, "Consumer"); + } + } + else // must be delayed response to Phase Operation. Cannot unmarshal as phase data model is unknown. + { + return payload; + } + } + private DelayedResponseReceipt extractDelayedReceiptInfo(DelayedBaseInfo delayedInfo) { @@ -278,6 +446,8 @@ private DelayedResponseReceipt extractDelayedReceiptInfo(DelayedBaseInfo delayed receipt.setZone(delayedInfo.getZone()); receipt.setContext(delayedInfo.getContext()); receipt.setServiceName(delayedInfo.getServiceName()); + receipt.setResourceID(delayedInfo.getResourceID()); + receipt.setPhaseName(delayedInfo.getPhaseName()); receipt.setServiceType(delayedInfo.getServiceType()); receipt.setRequestedAction(delayedInfo.getResponseAction()); receipt.setRelativeRequestURI(delayedInfo.getFullRelativeURL()); diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/QueueReaderInfo.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/QueueReaderInfo.java new file mode 100644 index 00000000..cc212ddf --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/QueueReaderInfo.java @@ -0,0 +1,77 @@ +/* + * QueueReaderInfo.java + * Created: 28 Aug 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.queue; + +import java.io.Serializable; +import java.util.List; +import java.util.concurrent.ExecutorService; + +/** + * This class is a simple POJO that holds the link between an ExecutorService and the list of "threads" that are managed. + * The "thread" list is a list of objects (runnable) that are managed with the Executor Service. The intend of this linkage + * is to allow to call specific methods of the "runnable" class at certain times, most commonly at shutdown to ensure + * threads are properly recycled and/or shut down. Within this framework there are a couple of places where this type of + * service is required, most notably for the Remote and Local Message Queue Readers. They need to be shutdown properly with + * a specific shutdown() method to ensure 'blocking waits' are resolved. + * + * @author Joerg Huber + */ +public class QueueReaderInfo implements Serializable +{ + private static final long serialVersionUID = 4075125778176009331L; + + private ExecutorService service = null; + private List linkedClasses = null; + + public QueueReaderInfo() + { + this(null, null); + } + + public QueueReaderInfo(ExecutorService service) + { + this(service, null); + } + + public QueueReaderInfo(ExecutorService service, List linkedClasses) + { + super(); + setService(service); + setLinkedClasses(linkedClasses); + } + + public ExecutorService getService() + { + return service; + } + + public void setService(ExecutorService service) + { + this.service = service; + } + + public List getLinkedClasses() + { + return linkedClasses; + } + + public void setLinkedClasses(List linkedClasses) + { + this.linkedClasses = linkedClasses; + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/RemoteMessageQueueReader.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/RemoteMessageQueueReader.java index 154ab5e4..6a952346 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/RemoteMessageQueueReader.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/RemoteMessageQueueReader.java @@ -30,6 +30,7 @@ import sif3.common.header.HeaderValues.EventAction; import sif3.common.header.HeaderValues.MessageType; import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.HeaderValues.UpdateType; import sif3.common.header.ResponseHeaderConstants; import sif3.common.model.EventMetadata; @@ -62,16 +63,17 @@ public class RemoteMessageQueueReader implements Runnable { protected final Logger logger = LoggerFactory.getLogger(getClass()); - + private QueueInfo queueInfo = null; private ConsumerEnvironmentManager consumerEvnMgr = null; private SIF3Session sif3Session = null; private int readerID; private String lastMsgeID = null; private int waitTime = 0; // milliseconds + private boolean shutdownFlag = false; private MessageClient client = null; - + /** * Constructs a RemoteMessageQueueReader for the queue identified through the queueListenerInfo parameter for the given session and consumer * configuration. @@ -109,8 +111,8 @@ public RemoteMessageQueueReader(QueueInfo queueInfo, int readerID) throws Servic public void shutdown() { - // nothing to do at the moment - logger.debug("Shutdown Message Reader wit ID = " + getReaderID() + " for queue = " + getQueueInfo().getQueue().getName()); + logger.debug("Shutdown Message sent to Remote Reader with ID = " + getReaderID() + " for queue = " + getQueueInfo().getQueue().getName()); + shutdownFlag = true; } /* (non-Javadoc) @@ -120,7 +122,14 @@ public void shutdown() public void run() { logger.debug("Message Queue Reader "+getReaderID()+" starts reading messages for queue "+getQueueInfo().getQueue().getName()+"..."); - startReading(); + try + { + startReading(); + } + catch (InterruptedException ex) + { + logger.error("Thread interupted - application shutting down?", ex); + } } /*---------------------*/ @@ -129,11 +138,11 @@ public void run() /* * This is the actual core methods. It connect to the SIF Message queue and starts reading messages from it. */ - private void startReading() + private void startReading() throws InterruptedException { if (client != null) // indicating all good { - while (true) + while (!shutdownFlag) { try { @@ -154,12 +163,21 @@ else if (isError(response)) processMessage(response); } } - catch (ServiceInvokationException ex) - { - // Error should already have been logged. Just wait and try again - waitBeforeGetNext(); - } + catch (Exception ex) + { + logger.debug("Message Reader '" + getReaderID() + "' (ThreadID:"+Thread.currentThread().getId()+") shutdown flag: "+shutdownFlag); + // Error should already have been logged. Just wait and try again + if ((ex != null) && (ex instanceof InterruptedException)) + { + throw (InterruptedException) ex; + } + else + { + waitBeforeGetNext(); + } + } } + logger.debug("Message Reader '" + getReaderID() + "' (ThreadID:"+Thread.currentThread().getId()+") stopped reading messages. Shutdown flag: "+shutdownFlag); } } @@ -205,22 +223,57 @@ private boolean isError(Response response) return false; } - private void waitBeforeGetNext() + private void waitBeforeGetNext() //throws Exception { - logger.debug("\n==========================\n"+getReaderID()+" for queue "+getQueueInfo().getQueue().getName()+ " will wait for "+getWaitTime()/CommonConstants.MILISEC+" seconds before attempting to get next message."+"\n=========================="); + logger.debug("\n==========================\nRemote Reader "+getReaderID()+" for queue "+getQueueInfo().getQueue().getName()+ " will wait for "+getWaitTime()/CommonConstants.MILISEC+" seconds before attempting to get next message."+"\n=========================="); try { - Object semaphore = new Object(); - synchronized (semaphore) - { - semaphore.wait(getWaitTime()); - } + // For efficient shutdown purpose and the way semaphores work we only sleep for a max of 5 sec. and then go + // back to sleep if the waitTime isn't reached, yet. However if the shutdown flag is set we won't go back to + // sleep and finish off instead. + + // How many intervals of 5 secs to we have. + long intervals = getWaitTime() / CommonConstants.MAX_SLEEP_MILLISEC; + long reminder = getWaitTime() - (intervals * CommonConstants.MAX_SLEEP_MILLISEC); + + logger.debug("Num Intervals: "+intervals); + logger.debug("Reminder in Secs: "+reminder/1000); + + for (long i = 0; i < intervals; i++) + { + doWait(CommonConstants.MAX_SLEEP_MILLISEC); + if (shutdownFlag) + { + break; + } + } + if ((!shutdownFlag) && (reminder > 0)) + { + doWait(reminder); + } } catch (Exception ex) { logger.error("Blocking wait in Message Reader '" + getReaderID() + "' for queue: " + getQueueInfo().getQueue().getName() + " interrupted: " + ex.getMessage(), ex); } } + + private void doWait(long millisecs) + { + Object semaphore = new Object(); + synchronized (semaphore) + { + try + { + logger.debug("Message Reader '" + getReaderID() + "' has mini sleep for " + millisecs/1000 + " seconds"); + semaphore.wait(millisecs); + } + catch (InterruptedException ex) + { + logger.debug("Message Reader '" + getReaderID() + "' mini sleep interupted...: " + ex.getMessage()); + } + } + } /* * This is the main method that deals with processing a message that has been received. @@ -312,7 +365,10 @@ private void processError(Response response) //Populate ErrorInfo Object with base data. ErrorInfo errorInfo = new ErrorInfo(); setResponseBaseData(errorInfo, response, MessageType.ERROR); - + + // response already has the error type! + errorInfo.setErrorDetail(response.getError()); + // Is there a subscription for this Delayed ERROR type LocalConsumerQueue localQueue = getQueueInfo().getLocalConsumerQueue(errorInfo.getZone().getId(), errorInfo.getContext().getId(), errorInfo.getServiceName(), errorInfo.getServiceType().name()); @@ -340,17 +396,17 @@ private void processError(Response response) private void setResponseBaseData(DelayedBaseInfo baseInfo, Response response, MessageType messageType) { //First get all the info out from the relativeServicePath - URIPathInfo pathInfo = new URIPathInfo(getHeaderValue(response, ResponseHeaderConstants.HDR_REL_SERVICE_PATH)); + URIPathInfo pathInfo = new URIPathInfo(getHeaderValue(response, ResponseHeaderConstants.HDR_REL_SERVICE_PATH), getHeaderValue(response, ResponseHeaderConstants.HDR_SERVICE_TYPE)); baseInfo.setMessageType(messageType); baseInfo.setRequestGUID(getHeaderValue(response, ResponseHeaderConstants.HDR_REQUEST_ID)); baseInfo.setServiceType(pathInfo.getServiceType()); + baseInfo.setResourceID(pathInfo.getResourceID()); + baseInfo.setPhaseName(pathInfo.getPhaseName()); baseInfo.setPayload((String)response.getDataObject()); baseInfo.setMediaType(response.getMediaType()); baseInfo.setZone(getSif3Session().getZone(pathInfo.getZone())); baseInfo.setContext(getSif3Session().getContext(pathInfo.getContext())); -// baseInfo.setZone(getZone(pathInfo.getZone() != null ? pathInfo.getZone().getId() : null)); -// baseInfo.setContext(getContext(pathInfo.getContext()!= null ? pathInfo.getContext().getId() : null)); baseInfo.setMessageQueueReaderID(getQueueReaderID()); baseInfo.setFullRelativeURL(pathInfo.getOriginalURLString()); baseInfo.setServiceName(pathInfo.getServiceName()); @@ -399,6 +455,7 @@ private void processEvent(Response response) EventInfo eventInfo = new EventInfo(eventPayload, response.getMediaType(), eventAction, updateType, zone, context, metadata, getQueueReaderID()); eventInfo.setFingerprint(getHeaderValue(response, ResponseHeaderConstants.HDR_FINGERPRINT)); + eventInfo.setServiceType(StringUtils.isEmpty(serviceType) ? ServiceType.OBJECT : ServiceType.valueOf(serviceType)); logger.debug(getQueueReaderID()+": Attempts to push Event to local queue..."); localQueue.blockingPush(eventInfo); diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/connectors/ConsumerSubscriptionConnector.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/connectors/ConsumerSubscriptionConnector.java index 38df223c..15ff8eb6 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/connectors/ConsumerSubscriptionConnector.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/connectors/ConsumerSubscriptionConnector.java @@ -28,9 +28,9 @@ import sif3.common.CommonConstants.AdapterType; import sif3.common.exception.PersistenceException; import sif3.common.exception.ServiceInvokationException; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.ServiceInfo; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; import sif3.common.model.SubscriptionKey; import sif3.common.persist.model.SIF3Queue; import sif3.common.persist.model.SIF3Session; diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/DelayedBaseInfo.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/DelayedBaseInfo.java index 3c8427a8..e2c942ad 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/DelayedBaseInfo.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/DelayedBaseInfo.java @@ -45,7 +45,13 @@ public class DelayedBaseInfo extends QueueMessage implements Serializable // The Service Name of the request (StudentPersonals, SchoolInfos/{}/StudentPersonals etc are valid names) private String serviceName = null; - + + // Job Phases or potentially future other service types may have a resourceID for which the delayed response is for. + private String resourceID = null; + + // Functional Services can have delayed request/response for phases. + private String phaseName = null; + // The Service Name with real URL (StudentPersonals, SchoolInfos/1234-8826ABCD-1234/StudentPersonals etc are valid values) private String urlService = null; @@ -83,6 +89,25 @@ public void setServiceName(String serviceName) this.serviceName = serviceName; } + public String getResourceID() + { + return resourceID; + } + + public void setResourceID(String resourceID) + { + this.resourceID = resourceID; + } + + public String getPhaseName() + { + return phaseName; + } + + public void setPhaseName(String phaseName) + { + this.phaseName = phaseName; + } public HeaderProperties getHttpHeaders() { @@ -151,11 +176,10 @@ public void setQueryParameters(URLQueryParameter queryParameters) @Override public String toString() { - return "DelayedBaseInfo [requestGUID=" + requestGUID - + ", fullRelativeURL=" + fullRelativeURL + ", serviceName=" - + serviceName + ", urlService=" + urlService - + ", responseAction=" + responseAction + ", httpHeaders=" - + httpHeaders + ", queryParameters=" + queryParameters - + ", toString()=" + super.toString() + "]"; + return "DelayedBaseInfo [requestGUID=" + requestGUID + ", fullRelativeURL=" + + fullRelativeURL + ", serviceName=" + serviceName + ", resourceID=" + resourceID + + ", phaseName=" + phaseName + ", urlService=" + urlService + ", responseAction=" + + responseAction + ", httpHeaders=" + httpHeaders + ", queryParameters=" + + queryParameters + ", toString()=" + super.toString() + "]"; } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/ErrorInfo.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/ErrorInfo.java index ba6e6c0b..4ef71c2b 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/ErrorInfo.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/queue/types/ErrorInfo.java @@ -18,6 +18,8 @@ import java.io.Serializable; +import sif3.common.ws.ErrorDetails; + /** * This class is a utility type to allow a consumer to package the actual ERROR data and related * information in one structure. This type uses the actual ERROR payload data as a String as it has been @@ -33,4 +35,22 @@ public class ErrorInfo extends DelayedBaseInfo implements Serializable { private static final long serialVersionUID = 626191573052681937L; + + private ErrorDetails errorDetail = null; + + public ErrorDetails getErrorDetail() + { + return errorDetail; + } + + public void setErrorDetail(ErrorDetails errorDetail) + { + this.errorDetail = errorDetail; + } + + @Override + public String toString() + { + return "ErrorInfo [errorDetail=" + errorDetail + ", toString()=" + super.toString() + "]"; + } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/BaseResource.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/BaseResource.java index e00b5d37..1d8f91e3 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/BaseResource.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/BaseResource.java @@ -53,16 +53,19 @@ import sif3.common.header.HeaderValues.QueryIntention; import sif3.common.header.HeaderValues.RequestType; import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.RequestHeaderConstants; import sif3.common.header.ResponseHeaderConstants; +import sif3.common.interfaces.ChangesSinceProvider; +import sif3.common.interfaces.Provider; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.AuthenticationInfo; import sif3.common.model.PagingInfo; import sif3.common.model.RequestMetadata; import sif3.common.model.ResponseParameters; import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; import sif3.common.model.URLQueryParameter; import sif3.common.model.security.InternalSecurityServiceConstants; import sif3.common.model.security.SecurityServiceInfo; @@ -319,11 +322,49 @@ public SIFContext getSifContext() return sifContext; } + /** + * This method gets the ZONE from the request. If it is null it will look up the default zone and return that one. This ensures + * that always a zone value is available + * + * @return Zone from Request or Default Zone if not provided in request. + */ + public SIFZone getNotNullSIFZone() + { + SIFZone sifZone = getSifZone(); + if (sifZone == null) // default zone => Get default zone from session + { + SIF3Session session = getSIF3SessionForRequest(); + if (session != null) + { + sifZone = session.getDefaultZone(); + } + } + + return sifZone; + } + public void setSifContext(SIFContext sifContext) { this.sifContext = sifContext; } + /** + * This method gets the CONTEXT from the request. If it is null it will set it ti the default context and return that one. This ensures + * that always a context value is available + * + * @return Context from Request or Default Context if not provided in request. + */ + public SIFContext getNotNullSIFContext() + { + SIFContext sifContext = getSifContext(); + if (sifContext == null) // Default Context + { + sifContext = CommonConstants.DEFAULT_CONTEXT; + } + + return sifContext; + } + public boolean isSecure() { return this.isSecure; @@ -454,7 +495,18 @@ public Response makeResopnseWithNoContent(boolean isError, ResponseAction respon { return makeFullResponse(null, Status.NO_CONTENT.getStatusCode(), null, isError, responseAction, customResponseParams, null); } - + + /** + * Create a HTTP Response without any content and a HTTP Status of 200 (OK). It will set some header properties as defined + * in the SIF3 spec. + * + * @return A HTTP Response to be sent back to the client. + */ + public Response makeOKResopnseWithNoContent(ResponseAction responseAction, ResponseParameters customResponseParams) + { + return makeFullResponse(null, Status.NO_CONTENT.getStatusCode(), null, false, responseAction, customResponseParams, null); + } + /** * This method creates a HTTP response that adheres to the SIF3 specification. * @@ -477,14 +529,15 @@ public Response makeResponse(Object data, int status, boolean isError, ResponseA * @param data The data (payload) that shall be put into the response. * @param pagingInfo Paging Information to be added to the response header. * @param isError Indicator if the response is an error or a standard response. + * @param responseAction Indicator if the response action is QUERY or HEAD or something eles. * @param customResponseParams Set of custom http headers to be added to the response. * @param marshaller The marshaller that converts the 'data' into a valid media type. * * @return A HTTP Response to be sent back to the client. */ - public Response makePagedResponse(Object data, PagingInfo pagingInfo, boolean isError, ResponseParameters customResponseParams, MarshalFactory marshaller) + public Response makePagedResponse(Object data, PagingInfo pagingInfo, boolean isError, ResponseAction responseAction, ResponseParameters customResponseParams, MarshalFactory marshaller) { - return makeFullResponse(data, Status.OK.getStatusCode(), pagingInfo, isError, ResponseAction.QUERY, customResponseParams, marshaller); + return makeFullResponse(data, Status.OK.getStatusCode(), pagingInfo, isError, responseAction, customResponseParams, marshaller); } /* @@ -582,6 +635,14 @@ public Response makeDeleteMultipleResponse(List operationStatus } return makeResponse(deleteManyResponse, overallStatus.getStatusCode(), false, ResponseAction.DELETE, customResponseParams, infraMarshaller); } + + /* + * Helper Response for not yet supported functionality. + */ + public Response makeNotImplementedResponse(ResponseAction responseAction, ResponseParameters customResponseParams) + { + return makeErrorResponse(new ErrorDetails(CommonConstants.NOT_IMPLEMENTED, "Not Implemented, yet", "This functionality is not yet supported by this provider.", "Provider"), responseAction , customResponseParams); + } /*-----------------------*/ /*-- Protected Methods --*/ @@ -619,7 +680,54 @@ protected List getResourceIDsFromDeleteRequest(String deletePayload) thr return resourceIDs; } - /** + /* + * IllegalArgumentException if page number is <=0 which is not valid. + */ + protected PagingInfo getPagingInfo() throws IllegalArgumentException + { + PagingInfo pagingInfo = new PagingInfo(getSIFHeaderProperties(), getQueryParameters()); + if (pagingInfo.getPageSize() <= PagingInfo.NOT_DEFINED) // page size not defined. Pass null to provider. + { + pagingInfo = null; + } + else if (pagingInfo.getCurrentPageNo() <= 0) + { + throw new IllegalArgumentException("Page Number to be returned was set to "+pagingInfo.getCurrentPageNo()+". Must be "+CommonConstants.FIRST_PAGE+" or higher."); + } + else + { + pagingInfo = pagingInfo.clone(); // ensure that initial values are not overridden in case we need them later, + } + return pagingInfo; + } + + /* + * Can return null if the changesSinceMarker is not given. In this case we can also assume that the call + * is not for a changeSince request. + */ + protected String getChangesSinceMarker() + { + return getQueryParameters().getQueryParam(CommonConstants.CHANGES_SINCE_MARKER_NAME); + } + + /* + * If the given provider implements the ChangesSinceProvider then a casted provider is returned otherwise + * null is returned. + */ + protected ChangesSinceProvider getChangesSinceProvider(Provider provider) + { + if (ChangesSinceProvider.class.isAssignableFrom(provider.getClass())) + { + return (ChangesSinceProvider)provider; + } + else + { + return null; + } + } + + + /** * This method returns a default ResponseParamtere object. It has the httpHeaderParams property defaulted to the values listed * in the "adapter.custom.response.headers" property of the providers property file. This method will never return null. * @@ -639,6 +747,7 @@ protected ResponseParameters getInitialCustomResponseParameters() * rights for the requested service are at expected levels. * * @param serviceName Service for which the access rights shall be checked. + * @param serviceType The type of the service. Eg. OBJECT, SERVICEPATH, FUNCTIONAL ... * @param right The access right (QUERY, UPDATE etc) that shall be checked for. * @param accessType The access level (SUPPORTED, APPROVED, etc) that must be met for the given service and right. * @param allowDelayed TRUE then the request operation allows delayed requests. In a DIRECT environment it is not supported at @@ -649,7 +758,7 @@ protected ResponseParameters getInitialCustomResponseParameters() * * @return See desc */ - protected ErrorDetails validClient(String serviceName, AccessRight right, AccessType accessType, boolean allowDelayed, boolean autoCreateAllowed) + protected ErrorDetails validClient(String serviceName, ServiceType serviceType, AccessRight right, AccessType accessType, boolean allowDelayed, boolean autoCreateAllowed) { ErrorDetails error = validateSession(autoCreateAllowed); if (error != null) @@ -690,7 +799,7 @@ protected ErrorDetails validClient(String serviceName, AccessRight right, Access SIFZone zone = getSifZone(); //Check access rights - if (!sif3Session.hasAccess(right, accessType, serviceName, zone, context)) + if (!sif3Session.hasAccess(right, accessType, serviceName, serviceType, zone, context)) { String zoneID = (zone == null) ? "Default" : zone.getId(); String contextID = (context == null) ? CommonConstants.DEFAULT_CONTEXT_NAME : context.getId(); @@ -851,6 +960,15 @@ protected boolean validateTokenWithSecurityService(String securityToken) throws /*-------------------------------*/ /*-- Protected Utility Methods --*/ /*-------------------------------*/ + + /* + * Checks if the "mustUseAdvisory" flag is set in the request. Returns true if set (eg. must use id given in object) or + * false if set to false or missing (eg. provider must allocate new id). + */ + protected boolean getAdvisory() + { + return Boolean.valueOf(getSIFHeaderProperties().getHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, "false")); + } /* * This methods attempts to extract some request information that should be passed to the provider. There is a distinct set @@ -1408,7 +1526,14 @@ private Response makeFullResponse(Object data, int status, PagingInfo pagingInfo // and hope the client can recover, is to use the marshaller's default media type. finalMediaType = (marshaller.isSupported(finalMediaType)) ? finalMediaType : marshaller.getDefault(); } - String payload = marshaller.marshal(data, finalMediaType); + + boolean isAlreadyMarshalled = (data instanceof String); // indicates that data is already marshalled. + + String payload = null; + if (!isAlreadyMarshalled) // if not marshalled then do it now + { + payload = marshaller.marshal(data, finalMediaType); + } //System.out.println("==============================\nRespopnse Payload:\n"+payload+"\nSize String= "+payload.length()+"\nSize Bytes = "+payload.getBytes("UTF-8").length+"\n=============================="); //May want to reconsider if we really care about the content length... @@ -1416,7 +1541,7 @@ private Response makeFullResponse(Object data, int status, PagingInfo pagingInfo // response = Response.status(status).entity(content); // allHeaders.setHeaderProperty(ResponseHeaderConstants.HDR_CONTENT_LENGTH, String.valueOf(content.length)); - response = Response.status(status).entity(payload); + response = Response.status(status).entity(isAlreadyMarshalled ? data : payload); allHeaders.setHeaderProperty(HttpHeaders.CONTENT_TYPE, finalMediaType.toString()); } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/DataModelResource.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/DataModelResource.java index 5d3aa3c9..968f77b1 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/DataModelResource.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/DataModelResource.java @@ -35,40 +35,38 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import au.com.systemic.framework.utils.StringUtils; import sif3.common.CommonConstants; import sif3.common.conversion.MarshalFactory; import sif3.common.conversion.ModelObjectInfo; import sif3.common.conversion.UnmarshalFactory; -import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnmarshalException; import sif3.common.exception.UnsupportedMediaTypeExcpetion; -import sif3.common.exception.UnsupportedQueryException; import sif3.common.header.HeaderProperties; import sif3.common.header.HeaderValues; import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; import sif3.common.header.RequestHeaderConstants; import sif3.common.header.ResponseHeaderConstants; import sif3.common.interfaces.ChangesSinceProvider; import sif3.common.interfaces.Provider; import sif3.common.interfaces.QueryProvider; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.ChangedSinceInfo; import sif3.common.model.PagingInfo; import sif3.common.model.ResponseParameters; -import sif3.common.model.SIFContext; -import sif3.common.model.SIFZone; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; -import sif3.common.persist.model.SIF3Session; import sif3.common.ws.CreateOperationStatus; import sif3.common.ws.ErrorDetails; import sif3.common.ws.OperationStatus; import sif3.infra.common.env.mgr.ProviderManagerFactory; import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; import sif3.infra.common.interfaces.EnvironmentManager; +import sif3.infra.rest.provider.BaseProvider; import sif3.infra.rest.provider.ProviderFactory; import sif3.infra.rest.resource.helper.ServicePathQueryParser; -import au.com.systemic.framework.utils.StringUtils; /** * This is the generic implementation of all Object resources. It implements all the functions required by the SIF3 specification @@ -135,7 +133,7 @@ public DataModelResource(@Context UriInfo uriInfo, } // Provider Factory should already be initialised. If not it will be done now... - provider = ProviderFactory.getInstance().getProvider(new ModelObjectInfo(this.dmObjectNamePlural, null)); + provider = (BaseProvider) ProviderFactory.getInstance().getProvider(new ModelObjectInfo(this.dmObjectNamePlural, null)); if (provider != null) { determineMediaTypes(provider.getMarshaller(), provider.getUnmarshaller(), false); @@ -189,9 +187,6 @@ public UnmarshalFactory getUnmarshaller() // -------------------------------------------------// @POST @Path("{dmObjectNameSingle:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) // only these are possible returns. public Response createSingle(String payload, @PathParam("dmObjectNameSingle") String dmObjectNameSingle, @PathParam("mimeType") String mimeType) { if (logger.isDebugEnabled()) @@ -201,7 +196,7 @@ public Response createSingle(String payload, @PathParam("dmObjectNameSingle") St ResponseParameters responseParam = getInitialCustomResponseParameters(); - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.CREATE), AccessType.APPROVED, false, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.CREATE), AccessType.APPROVED, false, true); if (error != null) // Not allowed to access! { return makeErrorResponse(error, ResponseAction.CREATE, responseParam); @@ -215,7 +210,7 @@ public Response createSingle(String payload, @PathParam("dmObjectNameSingle") St try { - Object returnObj = provider.createSingle(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), getAdvisory(), getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + Object returnObj = provider.createSingle(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), getAdvisory(), getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); return makeResponse(returnObj, Status.CREATED.getStatusCode(), false, ResponseAction.CREATE, responseParam, provider.getMarshaller()); } @@ -231,12 +226,17 @@ public Response createSingle(String payload, @PathParam("dmObjectNameSingle") St { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal the given data to "+provider.getSingleObjectClassInfo().getObjectName()+". Problem reported: "+ex.getMessage()), ResponseAction.CREATE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } } @POST - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response createMany(String payload) { // Check what is really required: GET (QBE functionality) or POST (Create functionality) @@ -254,7 +254,7 @@ public Response createMany(String payload) } } - ErrorDetails error = validClient(dmObjectNamePlural, ((isQBE) ? getRight(AccessRight.QUERY) : getRight(AccessRight.CREATE)), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, ((isQBE) ? getRight(AccessRight.QUERY) : getRight(AccessRight.CREATE)), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { logger.debug("Error Found: "+error); @@ -275,9 +275,6 @@ public Response createMany(String payload) // --------------------------------------------------------// @GET @Path("{resourceID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response getSingle(@PathParam("resourceID") String resourceID, @PathParam("mimeType") String mimeType) { if (logger.isDebugEnabled()) @@ -287,7 +284,7 @@ public Response getSingle(@PathParam("resourceID") String resourceID, @PathParam ResponseParameters responseParam = getInitialCustomResponseParameters(); - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.QUERY), AccessType.APPROVED, false, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.QUERY), AccessType.APPROVED, false, true); if (error != null) // Not allowed to access! { return makeErrorResponse(error, ResponseAction.QUERY, responseParam); @@ -301,7 +298,7 @@ public Response getSingle(@PathParam("resourceID") String resourceID, @PathParam try { - Object returnObj = provider.retrievByPrimaryKey(resourceID, getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + Object returnObj = provider.retrievByPrimaryKey(resourceID, getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); if (returnObj != null) { @@ -309,7 +306,7 @@ public Response getSingle(@PathParam("resourceID") String resourceID, @PathParam } else { - return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+" does not exist."), ResponseAction.QUERY, responseParam); + return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+" does not exist."), ResponseAction.QUERY, responseParam); } } catch (PersistenceException ex) @@ -318,15 +315,20 @@ public Response getSingle(@PathParam("resourceID") String resourceID, @PathParam } catch (IllegalArgumentException ex) { - return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve "+provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.QUERY, responseParam); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve "+provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.QUERY, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } } @GET @Path("{resourceId:([^\\./]*)}/{remainingPath:.*}") - // Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response getServicePathQuery() { if (logger.isDebugEnabled()) @@ -341,7 +343,7 @@ public Response getServicePathQuery() return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Invalid service path"), ResponseAction.QUERY, responseParam); } - ErrorDetails error = validClient(parser.getServicePath(), getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(parser.getServicePath(), ServiceType.SERVICEPATH, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { return makeErrorResponse(error, ResponseAction.QUERY, responseParam); @@ -364,9 +366,9 @@ public Response getServicePathQuery() } else { - Object returnObj = QueryProvider.class.cast(provider).retrieveByServicePath(parser.getQueryCriteria(), getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); + Object returnObj = QueryProvider.class.cast(provider).retrieveByServicePath(parser.getQueryCriteria(), getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); - return makePagedResponse(returnObj, pagingInfo, false, responseParam, provider.getMarshaller()); + return makePagedResponse(returnObj, pagingInfo, false, ResponseAction.QUERY, responseParam, provider.getMarshaller()); } } catch (PersistenceException ex) @@ -377,20 +379,17 @@ public Response getServicePathQuery() { return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); } - catch (UnsupportedQueryException ex) - { - return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); - } - catch (DataTooLargeException ex) - { - return makeErrorResponse(new ErrorDetails(CommonConstants.RESPONSE_TOO_LARGE, "Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); - } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } } @GET - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response getMany() { if (logger.isDebugEnabled()) @@ -400,7 +399,7 @@ public Response getMany() ResponseParameters responseParam = getInitialCustomResponseParameters(); - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { return makeErrorResponse(error, ResponseAction.QUERY, responseParam); @@ -433,17 +432,14 @@ public Response getMany() if (csProvider.changesSinceSupported()) { //Get new changes since marker if page = first page or no paging info - HeaderProperties customHeaders = null; String newChangesSinceMarker = null; if ((pagingInfo == null) || (pagingInfo.getCurrentPageNo() == CommonConstants.FIRST_PAGE)) { - newChangesSinceMarker = csProvider.getLatestOpaqueMarker(getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true)); - customHeaders = new HeaderProperties(); - customHeaders.setHeaderProperty(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, newChangesSinceMarker); + newChangesSinceMarker = csProvider.getLatestOpaqueMarker(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true)); } // Return the results. - Object returnObj = csProvider.getChangesSince(getSifZone(), getSifContext(), pagingInfo, new ChangedSinceInfo(changesSinceMarker), getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); + Object returnObj = csProvider.getChangesSince(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, new ChangedSinceInfo(changesSinceMarker), getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); // Check if we have pagingInfo parameter and if so if the navigationID is set. If it is not set we set it to the value of the // newChangesSinceMarker. Consumer can use this to identify which query the provider ran in subsequent paged queries. @@ -452,7 +448,13 @@ public Response getMany() pagingInfo.setNavigationId(newChangesSinceMarker); } - return makePagedResponse(returnObj, pagingInfo, false, responseParam, provider.getMarshaller()); + // Add changes since marker to response + if (newChangesSinceMarker != null) + { + responseParam.addHTTPHeaderParameter(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, newChangesSinceMarker); + } + + return makePagedResponse(returnObj, pagingInfo, false, ResponseAction.QUERY, responseParam, provider.getMarshaller()); } else // changes since is not supported => Error @@ -468,8 +470,8 @@ public Response getMany() } else // All good. { - Object returnObj = provider.retrieve(getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); - return makePagedResponse(returnObj, pagingInfo, false, responseParam, provider.getMarshaller()); + Object returnObj = provider.retrieve(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); + return makePagedResponse(returnObj, pagingInfo, false, ResponseAction.QUERY, responseParam, provider.getMarshaller()); } } } @@ -482,14 +484,14 @@ public Response getMany() { return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve "+provider.getMultiObjectClassInfo().getObjectName()+" with Paging Information: "+pagingInfo+". Problem reported: "+ex.getMessage()), ResponseAction.QUERY, responseParam); } - catch (UnsupportedQueryException ex) - { - return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Failed to retrieve "+provider.getMultiObjectClassInfo().getObjectName()+" with Paging Information: "+pagingInfo+". Problem reported: "+ex.getMessage()), ResponseAction.QUERY, responseParam); - } - catch (DataTooLargeException ex) - { - return makeErrorResponse(new ErrorDetails(CommonConstants.RESPONSE_TOO_LARGE, "Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); - } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } } @@ -498,9 +500,6 @@ public Response getMany() // ----------------------------------------------------------// @PUT @Path("{resourceID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response updateSingle(String payload, @PathParam("resourceID") String resourceID, @PathParam("mimeType") String mimeType) { if (logger.isDebugEnabled()) @@ -510,7 +509,7 @@ public Response updateSingle(String payload, @PathParam("resourceID") String res ResponseParameters responseParam = getInitialCustomResponseParameters(); - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.UPDATE), AccessType.APPROVED, false, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.UPDATE), AccessType.APPROVED, false, true); if (error != null) // Not allowed to access! { return makeErrorResponse(error, ResponseAction.UPDATE, responseParam); @@ -524,22 +523,22 @@ public Response updateSingle(String payload, @PathParam("resourceID") String res try { - if (provider.updateSingle(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), resourceID, getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam)) + if (provider.updateSingle(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), resourceID, getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam)) { return makeResopnseWithNoContent(false, ResponseAction.UPDATE, responseParam); } else { - return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+" does not exist."), ResponseAction.UPDATE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+" does not exist."), ResponseAction.UPDATE, responseParam); } } catch (PersistenceException ex) { - return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update "+provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update "+provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); } catch (IllegalArgumentException ex) { - return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update "+provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update "+provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); } catch (UnmarshalException ex) { @@ -549,12 +548,17 @@ public Response updateSingle(String payload, @PathParam("resourceID") String res { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal the given data to "+provider.getSingleObjectClassInfo().getObjectName()+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.UPDATE, responseParam); + } } @PUT - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response updateMany(String payload) { // Check what is really required: DELETE or UPDATE @@ -572,7 +576,7 @@ public Response updateMany(String payload) } } - ErrorDetails error = validClient(dmObjectNamePlural, ((doDelete) ? getRight(AccessRight.DELETE) : getRight(AccessRight.UPDATE)), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, ((doDelete) ? getRight(AccessRight.DELETE) : getRight(AccessRight.UPDATE)), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { logger.debug("Error Found: "+error); @@ -593,9 +597,6 @@ public Response updateMany(String payload) // -------------------------------------------------------------// @DELETE @Path("{resourceID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response removeSingle(@PathParam("resourceID") String resourceID, @PathParam("mimeType") String mimeType) { if (logger.isDebugEnabled()) @@ -605,7 +606,7 @@ public Response removeSingle(@PathParam("resourceID") String resourceID, @PathPa ResponseParameters responseParam = getInitialCustomResponseParameters(); - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.DELETE), AccessType.APPROVED, false, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.DELETE), AccessType.APPROVED, false, true); if (error != null) // Not allowed to access! { logger.debug("Error Found: "+error); @@ -620,29 +621,34 @@ public Response removeSingle(@PathParam("resourceID") String resourceID, @PathPa try { - if (provider.deleteSingle(resourceID, getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam)) + if (provider.deleteSingle(resourceID, getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam)) { return makeResopnseWithNoContent(false, ResponseAction.DELETE, responseParam); } else { - return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+" does not exist."), ResponseAction.DELETE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+" does not exist."), ResponseAction.DELETE, responseParam); } } catch (PersistenceException ex) { - return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to delete "+provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.DELETE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to delete "+provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.DELETE, responseParam); } catch (IllegalArgumentException ex) { - return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to delete "+provider.getSingleObjectClassInfo().getObjectName()+" with resouce ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.DELETE, responseParam); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to delete "+provider.getSingleObjectClassInfo().getObjectName()+" with resource ID = "+resourceID+". Problem reported: "+ex.getMessage()), ResponseAction.DELETE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.DELETE, responseParam); + } } @DELETE - //Let everything through and then deal with it when needed. -// @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) -// @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) /* * NOTE: * This method is not really implemented as DELETE is not supported with a payload. See PUT method for details about the way @@ -675,7 +681,7 @@ public Response getServiceInfo() logger.debug("Get Service Info (REST HEAD)"); } - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { return makeResponse(null, error.getErrorCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); @@ -692,7 +698,7 @@ public Response getServiceInfo() { pagingInfo = getPagingInfo(); HeaderProperties defaultCustomHeaders = getInitialCustomResponseParameters().getHttpHeaderParams(); - HeaderProperties customHeaders = provider.getServiceInfo(getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true)); + HeaderProperties customHeaders = provider.getServiceInfo(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true)); if (customHeaders != null) { // Copy customHeaders to defaultCustomHeaders to ensure the correct override order. @@ -710,13 +716,13 @@ public Response getServiceInfo() { if (csProvider.changesSinceSupported()) { - defaultCustomHeaders.setHeaderProperty(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, csProvider.getLatestOpaqueMarker(getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true))); + defaultCustomHeaders.setHeaderProperty(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, csProvider.getLatestOpaqueMarker(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true))); } } ResponseParameters responseParams = new ResponseParameters(defaultCustomHeaders); - return makePagedResponse(null, pagingInfo, false, responseParams, null); + return makePagedResponse(null, pagingInfo, false, ResponseAction.HEAD, responseParams, null); } catch (PersistenceException ex) { @@ -726,14 +732,11 @@ public Response getServiceInfo() { return makeResponse(null, Status.INTERNAL_SERVER_ERROR.getStatusCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); } - catch (UnsupportedQueryException ex) + catch (SIFException ex) { - return makeResponse(null, Status.BAD_REQUEST.getStatusCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + // No payload can be returned for HEAD method. Only return the status... + return makeResponse(null, ex.getErrorDetails().getErrorCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); } - catch (DataTooLargeException ex) - { - return makeResponse(null, CommonConstants.RESPONSE_TOO_LARGE, true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); - } } /* @@ -747,7 +750,7 @@ public Response getServicePathInfo() { logger.debug("Get Service Path Info (REST HEAD)"); } - ErrorDetails error = validClient(parser.getServicePath(), getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + ErrorDetails error = validClient(parser.getServicePath(), ServiceType.SERVICEPATH, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); if (error != null) // Not allowed to access! { return makeResponse(null, error.getErrorCode(), true, ResponseAction.QUERY, getInitialCustomResponseParameters(), null); @@ -767,7 +770,7 @@ public Response getSingleObjectServiceInfo() { logger.debug("Get Single Object Service Info (REST HEAD)"); } - ErrorDetails error = validClient(dmObjectNamePlural, getRight(AccessRight.QUERY), AccessType.APPROVED, false, true); + ErrorDetails error = validClient(dmObjectNamePlural, ServiceType.OBJECT, getRight(AccessRight.QUERY), AccessType.APPROVED, false, true); if (error != null) // Not allowed to access! { return makeResponse(null, error.getErrorCode(), true, ResponseAction.QUERY, getInitialCustomResponseParameters(), null); @@ -778,33 +781,33 @@ public Response getSingleObjectServiceInfo() /*------------------------*/ /*-- Overridden Methods --*/ /*------------------------*/ - @Override - public SIFZone getSifZone() - { - SIFZone sifZone = super.getSifZone(); - if (sifZone == null) // default zone => Get default zone from session - { - SIF3Session session = getSIF3SessionForRequest(); - if (session != null) - { - sifZone = session.getDefaultZone(); - } - } - - return sifZone; - } - - @Override - public SIFContext getSifContext() - { - SIFContext sifContext = super.getSifContext(); - if (sifContext == null) // Default Context - { - sifContext = CommonConstants.DEFAULT_CONTEXT; - } - - return sifContext; - } +// @Override +// public SIFZone getSifZone() +// { +// SIFZone sifZone = super.getSifZone(); +// if (sifZone == null) // default zone => Get default zone from session +// { +// SIF3Session session = getSIF3SessionForRequest(); +// if (session != null) +// { +// sifZone = session.getDefaultZone(); +// } +// } +// +// return sifZone; +// } +// +// @Override +// public SIFContext getSifContext() +// { +// SIFContext sifContext = super.getSifContext(); +// if (sifContext == null) // Default Context +// { +// sifContext = CommonConstants.DEFAULT_CONTEXT; +// } +// +// return sifContext; +// } /*---------------------*/ /*-- Private Methods --*/ @@ -818,11 +821,6 @@ private Provider getProvider() return provider; } - private boolean getAdvisory() - { - return Boolean.valueOf(getSIFHeaderProperties().getHeaderProperty(RequestHeaderConstants.HDR_ADVISORY, "false")); - } - /* * This method is a helper to determine what the actual access right is. If a provider is a direct provider an access right is the actual * right of the consumer as set in the environment ACL. If the provider is in a brokered environment its right is the ACL in relation @@ -834,51 +832,51 @@ private AccessRight getRight(AccessRight directEnvRight) return getProviderEnvironment().getEnvironmentType() == EnvironmentType.DIRECT ? directEnvRight : AccessRight.PROVIDE; } - /* - * IllegalArgumentException if page number is <=0 which is not valid. - */ - private PagingInfo getPagingInfo() throws IllegalArgumentException - { - PagingInfo pagingInfo = new PagingInfo(getSIFHeaderProperties(), getQueryParameters()); - if (pagingInfo.getPageSize() <= PagingInfo.NOT_DEFINED) // page size not defined. Pass null to provider. - { - pagingInfo = null; - } - else if (pagingInfo.getCurrentPageNo() <= 0) - { - throw new IllegalArgumentException("Page Number to be returned was set to "+pagingInfo.getCurrentPageNo()+". Must be "+CommonConstants.FIRST_PAGE+" or higher."); - } - else - { - pagingInfo = pagingInfo.clone(); // ensure that initial values are not overridden in case we need them later, - } - return pagingInfo; - } +// /* +// * IllegalArgumentException if page number is <=0 which is not valid. +// */ +// private PagingInfo getPagingInfo() throws IllegalArgumentException +// { +// PagingInfo pagingInfo = new PagingInfo(getSIFHeaderProperties(), getQueryParameters()); +// if (pagingInfo.getPageSize() <= PagingInfo.NOT_DEFINED) // page size not defined. Pass null to provider. +// { +// pagingInfo = null; +// } +// else if (pagingInfo.getCurrentPageNo() <= 0) +// { +// throw new IllegalArgumentException("Page Number to be returned was set to "+pagingInfo.getCurrentPageNo()+". Must be "+CommonConstants.FIRST_PAGE+" or higher."); +// } +// else +// { +// pagingInfo = pagingInfo.clone(); // ensure that initial values are not overridden in case we need them later, +// } +// return pagingInfo; +// } - /* - * Can return null if the changesSinceMarker is not given. In this case we can also assume that the call - * is not for a changeSince request. - */ - private String getChangesSinceMarker() - { - return getQueryParameters().getQueryParam(CommonConstants.CHANGES_SINCE_MARKER_NAME); - } +// /* +// * Can return null if the changesSinceMarker is not given. In this case we can also assume that the call +// * is not for a changeSince request. +// */ +// private String getChangesSinceMarker() +// { +// return getQueryParameters().getQueryParam(CommonConstants.CHANGES_SINCE_MARKER_NAME); +// } - /* - * If the given provider implements the ChangesSinceProvider then a casted provider is returned otherwise - * null is returned. - */ - private ChangesSinceProvider getChangesSinceProvider(Provider provider) - { - if (ChangesSinceProvider.class.isAssignableFrom(provider.getClass())) - { - return (ChangesSinceProvider)provider; - } - else - { - return null; - } - } +// /* +// * If the given provider implements the ChangesSinceProvider then a casted provider is returned otherwise +// * null is returned. +// */ +// private ChangesSinceProvider getChangesSinceProvider(Provider provider) +// { +// if (ChangesSinceProvider.class.isAssignableFrom(provider.getClass())) +// { +// return (ChangesSinceProvider)provider; +// } +// else +// { +// return null; +// } +// } private Response updateMany(Provider provider, String payload) { @@ -892,7 +890,7 @@ private Response updateMany(Provider provider, String payload) } else { - List statusList = provider.updateMany(provider.getUnmarshaller().unmarshal(payload, provider.getMultiObjectClassInfo().getObjectType(), getRequestMediaType()), getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + List statusList = provider.updateMany(provider.getUnmarshaller().unmarshal(payload, provider.getMultiObjectClassInfo().getObjectType(), getRequestMediaType()), getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); if (statusList != null) { @@ -916,6 +914,14 @@ private Response updateMany(Provider provider, String payload) { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal the given data to "+provider.getSingleObjectClassInfo().getObjectName()+". Problem reported: "+ex.getMessage()), ResponseAction.UPDATE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+""); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.UPDATE, responseParam); + } } private Response deleteMany(Provider provider, String payload) @@ -930,7 +936,7 @@ private Response deleteMany(Provider provider, String payload) } else { - List statusList = provider.deleteMany(getResourceIDsFromDeleteRequest(payload), getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + List statusList = provider.deleteMany(getResourceIDsFromDeleteRequest(payload), getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); if (statusList != null) { @@ -954,6 +960,14 @@ private Response deleteMany(Provider provider, String payload) { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal the given data to DeleteRequestType. Problem reported: " + ex.getMessage()), ResponseAction.DELETE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+""); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.DELETE, responseParam); + } } private Response createMany(Provider provider, String payload) @@ -968,11 +982,10 @@ private Response createMany(Provider provider, String payload) } else { - List statusList = provider.createMany(provider.getUnmarshaller().unmarshal(payload, provider.getMultiObjectClassInfo().getObjectType(), getRequestMediaType()), getAdvisory(), getSifZone(), getSifContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + List statusList = provider.createMany(provider.getUnmarshaller().unmarshal(payload, provider.getMultiObjectClassInfo().getObjectType(), getRequestMediaType()), getAdvisory(), getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); if (statusList != null) { -// return makeCreateMultipleResponse(statusList, Status.CREATED, responseParam); return makeCreateMultipleResponse(statusList, Status.OK, responseParam); } else @@ -993,6 +1006,14 @@ private Response createMany(Provider provider, String payload) { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "Could not unmarshal the given data to "+provider.getSingleObjectClassInfo().getObjectName()+". Problem reported: "+ex.getMessage()), ResponseAction.CREATE, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+""); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } } private Response queryByQBE(Provider provider, String payload) @@ -1015,9 +1036,9 @@ private Response queryByQBE(Provider provider, String payload) } else { - Object returnObj = QueryProvider.class.cast(provider).retrieveByQBE(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), getSifZone(), getSifContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); + Object returnObj = QueryProvider.class.cast(provider).retrieveByQBE(provider.getUnmarshaller().unmarshal(payload, provider.getSingleObjectClassInfo().getObjectType(), getRequestMediaType()), getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); - return makePagedResponse(returnObj, pagingInfo, false, responseParam, provider.getMarshaller()); + return makePagedResponse(returnObj, pagingInfo, false, ResponseAction.QUERY, responseParam, provider.getMarshaller()); } } catch (PersistenceException ex) @@ -1032,18 +1053,18 @@ private Response queryByQBE(Provider provider, String payload) { return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "(QBE) Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); } - catch (UnsupportedQueryException ex) - { - return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "(QBE) Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); - } - catch (DataTooLargeException ex) - { - return makeErrorResponse(new ErrorDetails(CommonConstants.RESPONSE_TOO_LARGE, "(QBE) Failed to retrieve " + provider.getMultiObjectClassInfo().getObjectName() + " with Paging Information: " + pagingInfo + ". Problem reported: " + ex.getMessage()), ResponseAction.QUERY, responseParam); - } catch (UnsupportedMediaTypeExcpetion ex) { return makeErrorResponse(new ErrorDetails(Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), "(QBE) Could not unmarshal the given data to "+provider.getSingleObjectClassInfo().getObjectName()+". Problem reported: "+ex.getMessage()), ResponseAction.QUERY, responseParam); } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+provider.getMultiObjectClassInfo().getObjectName()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } } } diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/JobResource.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/JobResource.java new file mode 100644 index 00000000..113a013b --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/resource/JobResource.java @@ -0,0 +1,1651 @@ +/* + * JobResource.java + * Created: 30 Jan 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.resource; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.MatrixParam; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants; +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.conversion.ModelObjectInfo; +import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.ResourceNotFoundException; +import sif3.common.exception.SIFException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues; +import sif3.common.header.HeaderValues.ResponseAction; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.header.RequestHeaderConstants; +import sif3.common.header.ResponseHeaderConstants; +import sif3.common.interfaces.ChangesSinceProvider; +import sif3.common.interfaces.FunctionalServiceProvider; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; +import sif3.common.model.ChangedSinceInfo; +import sif3.common.model.PagingInfo; +import sif3.common.model.RequestMetadata; +import sif3.common.model.ResponseParameters; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.ServiceInfo; +import sif3.common.model.job.PhaseInfo; +import sif3.common.persist.model.SIF3Session; +import sif3.common.utils.UUIDGenerator; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.ErrorDetails; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.common.ws.job.PhaseDataResponse; +import sif3.infra.common.env.mgr.ProviderManagerFactory; +import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; +import sif3.infra.common.env.types.ExtendedJobInfo; +import sif3.infra.common.interfaces.EnvironmentManager; +import sif3.infra.common.interfaces.ProviderJobManager; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobStateType; +import sif3.infra.common.model.JobType; +import sif3.infra.common.model.PhaseStateType; +import sif3.infra.common.model.PhaseType; +import sif3.infra.common.model.RightType; +import sif3.infra.common.model.RightsType; +import sif3.infra.common.model.StateCollectionType; +import sif3.infra.common.model.StateType; +import sif3.infra.rest.provider.ProviderFactory; + +/** + * This is the resource implementation of the Job Service that covers the end-points for functional services. It caters for the actual + * Job Object, the phase end-points as well as for the status end-points. It implements all the functions required by the SIF3 specification + * for a functional service provider.

+ * + * Developers are not expected to use this class to write providers. It is a full and generic implementation. The developers are + * expected to extend the BaseJobProvider and then configure the rest in the provider's property file and job-template tables as + * described in the Developer's Guide. This class uses property files and the job template tables to correctly invoke specific + * JobProvider classes. Please refer to the developer's guide for detail on which classes need to be implemented to write a functional + * service provider.

+ * + * This class makes one assumption though, and that is that the base URI for all job providers is of the form:
+ * http:///services/...

+ * + * It must be ensured that in all the environments managed with this framework that the "services" connector URI follows this + * structure. + * + * @author Joerg Huber + */ +@Path("/services/{jobNamePlural:([^\\./]*)}{mimeType:(\\.[^/]*?)?}") +public class JobResource extends InfraResource +{ + private String jobNamePlural = null; // This is the same as the JOB_URL_NAME in the SIF3_JOB_TEMPLATE table. + private FunctionalServiceProvider fsProvider = null; + + /** + * @param uriInfo URI of service. + * @param requestHeaders Headers + * @param request Original request object + * @param zoneID Matrix Parameter Zone ID + * @param contextID Matrix Parameter Context ID + */ + public JobResource(@Context UriInfo uriInfo, + @Context HttpHeaders requestHeaders, + @Context Request request, + @PathParam("jobNamePlural") String jobNamePlural, + @PathParam("mimeType") String mimeType, + @MatrixParam("zoneId") String zoneID, + @MatrixParam("contextId") String contextID) + { + super(uriInfo, requestHeaders, request, "services", zoneID, contextID); + + setJobNamePlural(jobNamePlural); + if (logger.isDebugEnabled()) + { + logger.debug("Job Service to use: "+getJobNamePlural()); + logger.debug("Request Media Type : " + getRequestMediaType()); + logger.debug("Response Media Type: " + getResponseMediaType()); + } + + setFSProvider((FunctionalServiceProvider) ProviderFactory.getInstance().getProvider(new ModelObjectInfo(this.jobNamePlural, null))); + } + + /*----------------------*/ + /*-- Abstract Methods --*/ + /*----------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.rest.resource.BaseResource#getEnvironmentManager() + */ + @Override + public EnvironmentManager getEnvironmentManager() + { + return ProviderManagerFactory.getEnvironmentManager(); + } + + //---------------------------// + //-- Job End-Point Section --// + //---------------------------// + + // -------------------------------------------------// + // -- POST Section: This is the C(reate) in CRUD. --// + // -------------------------------------------------// + /* + * Creates a single Job. + */ + @POST + @Path("{jobNameSingle:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response createJob(String payload, @PathParam("jobNameSingle") String jobNameSingle, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("Create Single Job "+jobNameSingle+" (REST POST) with URL Postfix mimeType = '" + mimeType + "' and input data: " + payload); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.CREATE), AccessType.APPROVED, false, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + // Get the payload and marshal it into an object. From there we can retrieve potential initialisation parameters. + if (payload == null) // something is wrong! + { + error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Missing Job Data", "No Job Data provided in request for Job "+getJobNamePlural(), "Provider"); + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + JobType payloadJob = null; + try + { + payloadJob = (JobType)getInfraUnmarshaller().unmarshal(payload, JobType.class, getRequestMediaType()); + } + catch (Exception ex) + { + logger.error("Failed to unmarshal payload into an Job Data: "+ ex.getMessage()+"\n Offending payload:\n"+payload, ex); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal job payload: "+ ex.getMessage()), ResponseAction.CREATE, responseParam); + } + + try + { + JobType finalJob = createJobInternal(payloadJob, responseParam, true); + return makeResponse(finalJob, Status.CREATED.getStatusCode(), false, ResponseAction.CREATE, responseParam, getInfraMarshaller()); + + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + } + + /* + * Creates a multiple Jobs. + */ + @POST + public Response createJobs(String payload) + { + // Check what is really required: GET (QBE functionality) or POST (Create functionality) + boolean isQBE = HeaderValues.MethodType.GET.name().equalsIgnoreCase(getSIFHeaderProperties().getHeaderProperty(RequestHeaderConstants.HDR_METHOD_OVERRIDE)); + + if (logger.isDebugEnabled()) + { + if (isQBE) + { + logger.debug("QBE on "+getJobNamePlural()+" (REST POST, method OVERRIDE=GET) with input data: " + payload); + } + else + { + logger.debug("Create Multiple Jobs "+getJobNamePlural()+" (REST POST) with input data: " + payload); + } + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + if (isQBE) // not supported for Job Services in this framework. + { + return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Query By Example (QBE) not supported.", "Query By Example (QBE) not supported for "+getJobNamePlural()+".", "Provider"), ResponseAction.QUERY, responseParam); + } + + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.CREATE), AccessType.APPROVED, true, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + // Get the payload and marshal it into an object. From there we can retrieve potential initialisation parameters. + if (payload == null) // something is wrong! + { + error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Missing Job Data", "No Job Data provided in request for Job "+getJobNamePlural(), "Provider"); + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + JobCollectionType payloadJobs = null; + try + { + payloadJobs = (JobCollectionType)getInfraUnmarshaller().unmarshal(payload, JobCollectionType.class, getRequestMediaType()); + } + catch (Exception ex) + { + logger.error("Failed to unmarshal payload into an Job Collection Data: "+ ex.getMessage()+"\n Offending payload:\n"+payload, ex); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal job payload: "+ ex.getMessage()), ResponseAction.CREATE, responseParam); + } + + // Iterate through each job and do the same work as with a single job create request + ArrayList statusList = new ArrayList(); + for (JobType payloadJob : payloadJobs.getJob()) + { + try + { + JobType finalJob = createJobInternal(payloadJob, responseParam, true); + + //If we get here all is good. Make an entry in the statusList + statusList.add(new CreateOperationStatus(payloadJob.getId(), finalJob.getId(), Status.CREATED.getStatusCode())); + } + catch (SIFException ex) + { + // Add error to status list + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + statusList.add(new CreateOperationStatus(payloadJob.getId(), payloadJob.getId(), ex.getErrorDetails().getErrorCode(), ex.getErrorDetails())); + } + } + + return makeCreateMultipleResponse(statusList, Status.OK, responseParam); + } + + // ----------------------------------------------// + // -- GET Section: This is the R(ead) in CRUD. --// + // ----------------------------------------------// + @GET + @Path("{jobID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response getJob(@PathParam("jobID") String jobID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("Get Job by ID (REST GET - Single): "+jobID+" and URL Postfix mimeType = '"+mimeType+"'"); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.QUERY), AccessType.APPROVED, false, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.QUERY, responseParam); + } + + try + { + JobType job = getEnvironmentManager().getJobManager().retrieveJob(jobID); + if (job == null) + { + return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), "Job not found.", getJobNamePlural()+" Job with jobID = "+jobID+" does not exist.", "Provider"), ResponseAction.QUERY, responseParam); + } + else + { + return makeResponse(job, Status.OK.getStatusCode(), false, ResponseAction.QUERY, responseParam, getInfraMarshaller()); + } + } + catch (PersistenceException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve Job.", "Failed to retrieve Job for "+getJobNamePlural()+" and JobID = "+jobID+". Problem reported: "+ex.getMessage(), "Provider"), ResponseAction.QUERY, responseParam); + } + } + + @GET + public Response getJobs() + { + if (logger.isDebugEnabled()) + { + logger.debug("Get Jobs (REST GET - Plural)"); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.QUERY, responseParam); + } + + PagingInfo pagingInfo = null; + try + { + pagingInfo = getPagingInfo(); + if (pagingInfo == null) + { + throw new DataTooLargeException("Paging Information required.", "navigationPage and/or navigationPageSize HTTP Header(s) missing. Both must be set for this oprartion.", "Provider ("+getJobNamePlural()+")"); + } + + if (pretendDelayed()) + { + // Simply send a response with status of 202 + return makeDelayedAcceptResponse(ResponseAction.QUERY); + } + else + { + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), true); + ChangesSinceProvider provider = (ChangesSinceProvider)getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider"), ResponseAction.QUERY , responseParam); + } + + // We need to check if the changesSinceMarker has been provided. + String changesSinceMarker = getChangesSinceMarker(); + if (changesSinceMarker != null) // We have a ChangesSince request + { + if (provider.changesSinceSupported()) + { + //Get new changes since marker if page = first page + String newChangesSinceMarker = null; + if (pagingInfo.getCurrentPageNo() == CommonConstants.FIRST_PAGE) + { + newChangesSinceMarker = provider.getLatestOpaqueMarker(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, getRequestMetadata(getSIF3SessionForRequest(), true)); + } + + // Return the results. + Object returnObj = provider.getChangesSince(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, new ChangedSinceInfo(changesSinceMarker), getRequestMetadata(getSIF3SessionForRequest(), true), responseParam); + + // Check if we have pagingInfo parameter and if so if the navigationID is set. If it is not set we set it to the value of the + // newChangesSinceMarker. Consumer can use this to identify which query the provider ran in subsequent paged queries. + if (StringUtils.isEmpty(pagingInfo.getNavigationId()) && (newChangesSinceMarker != null)) + { + pagingInfo.setNavigationId(newChangesSinceMarker); + } + + // Add changes since marker to response + if (newChangesSinceMarker != null) + { + responseParam.addHTTPHeaderParameter(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, newChangesSinceMarker); + } + + return makePagedResponse(returnObj, pagingInfo, false, ResponseAction.QUERY, responseParam, getInfraMarshaller()); + } + else // changes since is not supported => Error + { + return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Provider for "+getJobNamePlural()+" does not support 'ChangesSince' functionality."), ResponseAction.QUERY, responseParam); + } + } + else // Standard request/response + { + JobCollectionType jobs = getEnvironmentManager().getJobManager().retrieveJobs(getJobNamePlural(), requestMetadata.getFingerprint(), getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo); + return makePagedResponse(jobs, pagingInfo, false, ResponseAction.QUERY, responseParam, getInfraMarshaller()); + } + } + } + catch (PersistenceException | IllegalArgumentException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not retrieve jobs.", "Failed to retrieve "+getJobNamePlural()+" with Paging Information: "+pagingInfo+". Problem reported: "+ex.getMessage(), "Provider"), ResponseAction.QUERY, responseParam); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } + } + + // ---------------------------------------------------// + // -- DELETE Section: This is the D(elete) in CRUD. --// + // ---------------------------------------------------// + @DELETE + @Path("{jobID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response removeJob(@PathParam("jobID") String jobID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("Remove Job "+getJobNamePlural()+" (REST DELETE) for Job with ID = "+jobID + " and URL Postfix mimeType = '" + mimeType + "'."); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.DELETE), AccessType.APPROVED, false, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.DELETE, responseParam); + } + + try + { + if (removeJobInternal(jobID, responseParam, true)) + { + return makeResopnseWithNoContent(false, ResponseAction.DELETE, responseParam); + } + else + { + return makeErrorResponse(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), "Job not found.", getJobNamePlural()+" Job with jobID = "+jobID+" does not exist.", "Provider"), ResponseAction.DELETE, responseParam); + } + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.DELETE, responseParam); + } + } + + @DELETE + /* + * NOTE: + * This method is not really implemented as DELETE is not supported with a payload. See PUT method for details about the way + * a Bulk-DELETE is implemented according to SIF3 Spec. + */ + public Response removeJobs(String payload) + { + if (logger.isDebugEnabled()) + { + logger.debug("Delete Collection of Jobs "+getJobNamePlural()+" (REST DELETE) with input data: " + payload); + } + ErrorDetails error = new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Operation not supported.", "Use HTTP PUT with header field '"+RequestHeaderConstants.HDR_METHOD_OVERRIDE+"' set to "+HeaderValues.MethodType.DELETE.name()+" instead."); + return makeErrorResponse(error, ResponseAction.DELETE, getInitialCustomResponseParameters()); + } + + // ------------------------------------------------// + // -- PUT Section: This is the U(pdate) in CRUD. --// + // ------------------------------------------------// + @PUT + @Path("{jobID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response updateJob(String payload, @PathParam("jobID") String jobID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("Update Single Job "+getJobNamePlural()+" (REST PUT) for Job with ID = "+jobID+", URL Postfix mimeType = "+mimeType+"' and input data: " + payload); + } + + ErrorDetails error = new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "The Job Object cannot be updated.", "Use updates to Phases or Status of a Job instead."); + return makeErrorResponse(error, ResponseAction.UPDATE, getInitialCustomResponseParameters()); + } + + @PUT + public Response updateJobs(String deletePayload) + { + // Check what is really required: DELETE or UPDATE + boolean doDelete = HeaderValues.MethodType.DELETE.name().equalsIgnoreCase(getSIFHeaderProperties().getHeaderProperty(RequestHeaderConstants.HDR_METHOD_OVERRIDE)); + + if (logger.isDebugEnabled()) + { + if (doDelete) // This is for delete of jobs. + { + logger.debug("Delete Jobs "+getJobNamePlural()+" (REST PUT, method OVERRIDE=DELETE) with input data: " + deletePayload); + } + else + { + ErrorDetails error = new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "The Job Objects cannot be updated.", "Use updates to Phases or Status of Job(s) instead."); + return makeErrorResponse(error, ResponseAction.UPDATE, getInitialCustomResponseParameters()); + } + } + + // If we get here the DELETE for a job collection is requested. + ResponseParameters responseParam = getInitialCustomResponseParameters(); + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.DELETE), AccessType.APPROVED, true, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.DELETE , responseParam); + } + + List jobIDs = null; + try + { + jobIDs = getResourceIDsFromDeleteRequest(deletePayload); + } + catch (Exception ex) + { + logger.error("Failed to unmarshal delete job payload into an JobID List: "+ ex.getMessage()+"\n Offending payload:\n"+deletePayload, ex); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal delete job payload: "+ ex.getMessage()), ResponseAction.DELETE, responseParam); + } + + // Iterate through each job and do the same work as with a single job create request + ArrayList statusList = new ArrayList(); + for (String jobID : jobIDs) + { + try + { + if (removeJobInternal(jobID, responseParam, true)) + { + statusList.add(new OperationStatus(jobID, Status.NO_CONTENT.getStatusCode())); + } + else + { + statusList.add(new OperationStatus(jobID, Status.NOT_FOUND.getStatusCode(), new ErrorDetails(Status.NOT_FOUND.getStatusCode(), "Job not found.", getJobNamePlural()+" Job with jobID = "+jobID+" does not exist.", "Provider"))); + } + } + catch (SIFException ex) + { + // Add error to status list + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + statusList.add(new OperationStatus(jobID, ex.getErrorDetails().getErrorCode(), ex.getErrorDetails())); + } + } + + return makeDeleteMultipleResponse(statusList, Status.OK, responseParam); + } + + + // -------------------// + // -- HEAD Section: --// + // -------------------// + + /* + * HEAD Method for root service ie. .../service/{jobNames}. This is the only fully supported HEAD method that returns + * all sort of things about the service including custom HTTP headers if set by the provider. + */ + @HEAD + public Response getServiceInfo() + { + if (logger.isDebugEnabled()) + { + logger.debug("Get Service Info (REST HEAD)"); + } + + ErrorDetails error = validClient(getJobNamePlural(), ServiceType.FUNCTIONAL, getRight(AccessRight.QUERY), AccessType.APPROVED, true, true); + if (error != null) // Not allowed to access! + { + return makeResponse(null, error.getErrorCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + } + + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeResponse(null, Status.SERVICE_UNAVAILABLE.getStatusCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + } + + PagingInfo pagingInfo = null; + try + { + pagingInfo = getPagingInfo(); + HeaderProperties defaultCustomHeaders = getInitialCustomResponseParameters().getHttpHeaderParams(); + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), true); + + HeaderProperties customHeaders = provider.getServiceInfo(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, requestMetadata); + if (customHeaders != null) + { + // Copy customHeaders to defaultCustomHeaders to ensure the correct override order. + defaultCustomHeaders.addHeaderProperties(customHeaders); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Custom headers to be returned from 'getServiceInfo()' method:\n"+customHeaders); + } + + // We know that the BaseFunctionalServiceProvider implements the ChangesSince Interface. Call appropriate methods here. + // Check if provider supports Changes Since and if so we need to get the latest opaque changes since marker. + ChangesSinceProvider csProvider = (ChangesSinceProvider)provider; + if (csProvider.changesSinceSupported()) + { + defaultCustomHeaders.setHeaderProperty(ResponseHeaderConstants.HDR_CHANGES_SINCE_MARKER, csProvider.getLatestOpaqueMarker(getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, requestMetadata)); + } + + ResponseParameters responseParams = new ResponseParameters(defaultCustomHeaders); + return makePagedResponse(null, pagingInfo, false, ResponseAction.HEAD, responseParams, null); + } + catch (PersistenceException ex) + { + return makeResponse(null, Status.INTERNAL_SERVER_ERROR.getStatusCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + } + catch (IllegalArgumentException ex) + { + return makeResponse(null, Status.INTERNAL_SERVER_ERROR.getStatusCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + } + catch (SIFException ex) + { + //HEAD cannot return payload. Only get status code and return that. + return makeResponse(null, ex.getErrorDetails().getErrorCode(), true, ResponseAction.HEAD, getInitialCustomResponseParameters(), null); + } + } + + + //-----------------------------// + //-- Phase End-Point Section --// + //-----------------------------// + + /* + * Retrieve Operation of a Phase. Note that the returned payload is an object defined in a binding document. Most likely a DM Object but not + * necessarily. Permissions must be checked against the Job & Phase. Also ensure that only the owner can request that operation which + * means the fingerprint must match! + */ + @GET + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response retrievePhaseObjects(@PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nRetrive Operation (HTTP GET) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + + //Let's get the full job data + ExtendedJobInfo jobInfo = null; + try + { + jobInfo = getFullJobInfo(jobID, true); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + ex.getErrorDetails().setDescription("Cannot retrieve data for phase "+phaseName+". Reason: "+ex.getErrorDetails().getDescription()); + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } + + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider"), ResponseAction.QUERY, responseParam); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), true); + ErrorDetails error = checkPhasePermission(jobInfo, phaseName, AccessRight.QUERY, provider, requestMetadata.getFingerprint(), true, false); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.QUERY, responseParam); + } + + // If all of the above is ok then we can start the real work... + PagingInfo pagingInfo = null; + try + { + pagingInfo = getPagingInfo(); + if (pretendDelayed()) + { + // Simply send a response with status of 202 + return makeDelayedAcceptResponse(ResponseAction.QUERY); + } + else + { + PhaseDataResponse response = provider.retrieveDataFromPhase(new PhaseInfo(jobID, phaseName), getNotNullSIFZone(), getNotNullSIFContext(), pagingInfo, requestMetadata, responseParam, getResponseMediaType()); + + return makePagedResponse((response != null ? response.getData() : null), pagingInfo, false, ResponseAction.QUERY, responseParam, null); + } + } + catch (PersistenceException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not retrieve data from Job Phase.", "Failed to retrieve data for Job Phase "+getJobNamePlural()+"/"+phaseName+" with Paging Information: "+pagingInfo+" (Job ID = "+jobID+"). Problem reported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), ResponseAction.QUERY, responseParam); + } + catch (UnsupportedMediaTypeExcpetion ex) + { + return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Could not retrieve data from Job Phase.", "Failed to retrieve data for Job Phase "+getJobNamePlural()+"/"+phaseName+" with Paging Information: "+pagingInfo+" (Job ID = "+jobID+"). Requested Mime type "+getResponseMediaType().toString()+" is not supported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), ResponseAction.QUERY, responseParam); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } + } + + /* + * Creates Operation of a Phase. Note that the payload is an object defined in a binding document. Most likely a DM Object but not + * necessarily. Data given in the payload is either to be created or is used to create implied data. + * Permissions must be checked against the Job & Phase. Also ensure that only the owner can request that operation which + * means the fingerprint must match! + */ + @POST + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response createPhaseObjects(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nCreate Operation (HTTP POST) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPhase Payload:\n" + payload); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + + //Let's get the full job data + ExtendedJobInfo jobInfo = null; + try + { + jobInfo = getFullJobInfo(jobID, true); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + ex.getErrorDetails().setDescription("Cannot create data for phase "+phaseName+". Reason: "+ex.getErrorDetails().getDescription()); + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider"), ResponseAction.CREATE, responseParam); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), false); + ErrorDetails error = checkPhasePermission(jobInfo, phaseName, AccessRight.CREATE, provider, requestMetadata.getFingerprint(), true, false); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + // If all of the above is ok then we can start the real work... + try + { + PhaseDataResponse response = provider.createDataInPhase(new PhaseInfo(jobID, phaseName), new PhaseDataRequest(payload, getRequestMediaType()), getAdvisory(), getNotNullSIFZone(), getNotNullSIFContext(), requestMetadata, responseParam, getResponseMediaType()); + Status finalStatus = Status.CREATED; + if ((response != null) && (response.getStatus() != null)) + { + finalStatus = response.getStatus(); + } + + return makeResponse((response != null ? response.getData() : null), finalStatus.getStatusCode(), false, ResponseAction.CREATE, responseParam, null); + } + catch (PersistenceException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not create data in Job Phase.", "Failed to create data for Job Phase "+getJobNamePlural()+"/"+phaseName+" (Job ID = "+jobID+"). Problem reported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), ResponseAction.CREATE, responseParam); + } + catch (UnsupportedMediaTypeExcpetion ex) + { + return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Could not create data in Job Phase.", "Failed to create data for Job Phase "+getJobNamePlural()+"/"+phaseName+" (Job ID = "+jobID+"). Requested Mime type "+getResponseMediaType().toString()+" is not supported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), ResponseAction.CREATE, responseParam); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + } + + /* + * Update Operation of a Phase. Note that the payload is an object defined in a binding document. Most likely a DM Object but not + * necessarily. Data given in the payload is either to be updated or is used to update implied data. This operation must also + * cover the delete as with standard Object services. + * Permissions must be checked against the Job & Phase. Also ensure that only the owner can request that operation which + * means the fingerprint must match! + */ + @PUT + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response updatePhaseObjects(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + // Check what is really required: DELETE or UPDATE + boolean doDelete = HeaderValues.MethodType.DELETE.name().equalsIgnoreCase(getSIFHeaderProperties().getHeaderProperty(RequestHeaderConstants.HDR_METHOD_OVERRIDE)); + + if (logger.isDebugEnabled()) + { + if (doDelete) // This is for delete of jobs. + { + logger.debug("\nDelete Operation (HTTP PUT, method OVERRODE=DELETE) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPhase Payload:\n" + payload); + } + else + { + logger.debug("\nUpdate Operation (HTTP PUT) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPhase Payload:\n" + payload); + } + } + AccessRight right = doDelete ? AccessRight.DELETE : AccessRight.UPDATE; + ResponseAction responseAction = doDelete ? ResponseAction.DELETE : ResponseAction.UPDATE; + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + + //Let's get the full job data + ExtendedJobInfo jobInfo = null; + try + { + jobInfo = getFullJobInfo(jobID, true); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + ex.getErrorDetails().setDescription("Cannot "+right.name()+" data for phase "+phaseName+". Reason: "+ex.getErrorDetails().getDescription()); + return makeErrorResponse(ex.getErrorDetails(), responseAction, responseParam); + } + + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider"), responseAction, responseParam); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), false); + ErrorDetails error = checkPhasePermission(jobInfo, phaseName, right, provider, requestMetadata.getFingerprint(), true, false); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, responseAction, responseParam); + } + + // If all of the above is ok then we can start the real work... + try + { + Status finalStatus = null; + PhaseDataResponse response = null; + if(doDelete) + { + response = provider.deleteDataInPhase(new PhaseInfo(jobID, phaseName), new PhaseDataRequest(payload, getRequestMediaType()), getNotNullSIFZone(), getNotNullSIFContext(), requestMetadata, responseParam, getResponseMediaType()); + finalStatus = Status.NO_CONTENT; + } + else + { + response = provider.updateDataInPhase(new PhaseInfo(jobID, phaseName), new PhaseDataRequest(payload, getRequestMediaType()), getNotNullSIFZone(), getNotNullSIFContext(), requestMetadata, responseParam, getResponseMediaType()); + finalStatus = Status.OK; + } + + if ((response != null) && (response.getStatus() != null)) + { + finalStatus = response.getStatus(); + } + + return makeResponse((response != null ? response.getData() : null), finalStatus.getStatusCode(), false, responseAction, responseParam, null); + } + catch (PersistenceException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Could not "+responseAction.name()+" data in Job Phase.", "Failed to "+responseAction.name()+" data for Job Phase "+getJobNamePlural()+"/"+phaseName+" (Job ID = "+jobID+"). Problem reported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), responseAction, responseParam); + } + catch (UnsupportedMediaTypeExcpetion ex) + { + return makeErrorResponse(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Could not "+responseAction.name()+" data in Job Phase.", "Failed to "+responseAction.name()+" data for Job Phase "+getJobNamePlural()+"/"+phaseName+" (Job ID = "+jobID+"). Requested Mime type "+getResponseMediaType().toString()+" is not supported: "+ex.getMessage(), "Provider ("+provider.getClass().getSimpleName()+")"), responseAction, responseParam); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), responseAction, responseParam); + } + } + + /* + * NOTE: + * This method is not really implemented as DELETE is not supported with a payload. See PUT method for details about the way + * a DELETE is implemented according to SIF3 Spec. + */ + @DELETE + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response removePhaseObjects(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nDelete Operation (HTTP DELETE) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPhase Payload:\n" + payload); + } + + ErrorDetails error = new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Operation not supported.", "Use HTTP PUT with header field '"+RequestHeaderConstants.HDR_METHOD_OVERRIDE+"' set to "+HeaderValues.MethodType.DELETE.name()+" instead."); + return makeErrorResponse(error, ResponseAction.DELETE, getInitialCustomResponseParameters()); + } + + //------------------------------// + //-- States End-Point Section --// + //------------------------------// + + /* + * Retrieve States of a Phase. Note that the returned payload is an collection of states. Ensure that only the owner can request that + * operation which means the fingerprint must match! + */ + @GET + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states{mimeType:(\\.[^/]*?)?}") + public Response retrievePhaseStates(@PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nRetrive States (HTTP GET) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + + //Let's get the full job data + ExtendedJobInfo jobInfo = null; + try + { + jobInfo = getFullJobInfo(jobID, true); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + ex.getErrorDetails().setDescription("Failed to retrieve states for phase "+phaseName+". Reason: "+ex.getErrorDetails().getDescription()); + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.QUERY, responseParam); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), true); + ErrorDetails error = checkPhasePermission(jobInfo, phaseName, AccessRight.QUERY, null, requestMetadata.getFingerprint(), false, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.QUERY, responseParam); + } + + try + { + StateCollectionType states = getStatesOfPhase(jobInfo, phaseName); + return makeResponse(states, Status.OK.getStatusCode(), false, ResponseAction.QUERY, responseParam, getInfraMarshaller()); + } + catch (PersistenceException ex) + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to access Job.", "Failed to access Job for "+getJobNamePlural()+" and JobID = "+jobID+". Problem reported: "+ex.getMessage(), "Provider"), ResponseAction.QUERY, responseParam); + } + } + + /* + * Creates a state for the selected phase and job. Note this should "add" a new state of the state list for the given job and phase. + * Note that the payload must be a State Object as defined infrastructure data model. Permissions must be checked against the Job, + * Phase and State. Also ensure that only the owner can request that operation which means the fingerprint must match! + */ + @POST + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states/state{mimeType:(\\.[^/]*?)?}") + public Response createPhaseState(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nCreate State (HTTP POST) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPayload:\n" + payload); + } + + ResponseParameters responseParam = getInitialCustomResponseParameters(); + + //Let's get the full job data + ExtendedJobInfo jobInfo = null; + try + { + jobInfo = getFullJobInfo(jobID, true); + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + ex.getErrorDetails().setDescription("Failed to add state to phase "+phaseName+". Reason: "+ex.getErrorDetails().getDescription()); + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider"), ResponseAction.CREATE, responseParam); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), false); + ErrorDetails error = checkPhasePermission(jobInfo, phaseName, AccessRight.CREATE, provider, requestMetadata.getFingerprint(), true, true); + if (error != null) // Not allowed to access! + { + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + // Get the payload and marshal it into an object. From there we can retrieve potential initialisation parameters. + if (payload == null) // something is wrong! + { + error = new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), "Missing State Data", "No State Data provided in request for Job "+getJobNamePlural(), "Provider"); + return makeErrorResponse(error, ResponseAction.CREATE, responseParam); + } + + StateType state = null; + try + { + state = (StateType)getInfraUnmarshaller().unmarshal(payload, StateType.class, getRequestMediaType()); + if (state.getType() == null) // not a valid value! Most likely invalid state string received. + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Invalid state recieved.","The consumer has provided an invlaid phase state. Valid values are: "+getValidPhaseStates(),"Provider"), ResponseAction.CREATE, responseParam); + } + } + catch (Exception ex) + { + logger.error("Failed to unmarshal payload into an State Type Data: "+ ex.getMessage()+"\n Offending payload:\n"+payload, ex); + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to unmarshal StateType payload: "+ ex.getMessage(), null, "Provider"), ResponseAction.CREATE, responseParam); + } + + // If all of the above is ok then we can start the real work... + try + { + // will update DB but also values in jobInfo + StateType newState = addPhaseStateByConsumer(jobInfo, phaseName, state); + if (newState != null) // we had a state update + { + // pass it to the specific provider class for additional processing. + try + { + provider.phaseStateUpdatedByConsumer(new PhaseInfo(jobID, phaseName), PhaseState.valueOf(newState.getType().value()), getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + } + catch (PersistenceException ex) + { + makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update state to phase.", getJobNamePlural()+": Failed to add state to phase "+phaseName+" for and JobID = "+jobID+".", "Provider ("+provider.getClass().getSimpleName()+")"), ResponseAction.CREATE, responseParam); + } + catch (SIFException ex) + { + makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + + // Finally create response... + return makeResponse(newState, Status.CREATED.getStatusCode(), false, ResponseAction.CREATE, responseParam, getInfraMarshaller()); + } + else + { + return makeErrorResponse(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(),"Failed to add state to phase.", getJobNamePlural()+": Failed to add state to phase "+phaseName+" for and JobID = "+jobID+".", "Provider"), ResponseAction.CREATE, responseParam); + } + } + catch (SIFException ex) + { + if (StringUtils.isEmpty(ex.getErrorDetails().getScope())) + { + ex.getErrorDetails().setScope("Provider ("+getJobNamePlural()+")"); + } + return makeErrorResponse(ex.getErrorDetails(), ResponseAction.CREATE, responseParam); + } + } + + //--------------------------------------------------------// + //-- SIF 3.x Spec. Unsupported States End-Point Section --// + //--------------------------------------------------------// + + /* + * Retrieve a State of a Phase and job is not supported as per SIF3.x specification because states don't have a refID. + */ + @GET + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states/{stateID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response retrievePhaseState(@PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("stateID") String stateID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nGet State (HTTP GET) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID+"\nState ID: "+ stateID +"\nURL Postfix mimeType: " + mimeType); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "A particular State of a Phase cannot be retrievd."), ResponseAction.QUERY, getInitialCustomResponseParameters()); + } + + + /* + * Batch creates of states is not allowed according to the SIF3.x specification + */ + @POST + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states{mimeType:(\\.[^/]*?)?}") + public Response createPhaseStates(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nCreate States (HTTP POST) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPayload:\n" + payload); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Batch States creation of a Phase not allowed."), ResponseAction.CREATE, getInitialCustomResponseParameters()); + } + + /* + * Update a state for the selected phase and job is not supported as per SIF3.x specification because states don't have a refID. + */ + @PUT + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states{mimeType:(\\.[^/]*?)?}") + public Response updatePhaseStates(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nUpdate States (HTTP POST) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPayload:\n" + payload); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "The States of a Phase cannot be updated."), ResponseAction.UPDATE, getInitialCustomResponseParameters()); + } + + /* + * Update a state for the selected phase and job is not supported as per SIF3.x specification because states don't have a refID. + */ + @PUT + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states/{stateID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response updatePhaseState(@PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("stateID") String stateID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nUpdate a State (HTTP UPDATE) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID+"\nState ID: "+ stateID +"\nURL Postfix mimeType: " + mimeType); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "A State of a Phase cannot be updated."), ResponseAction.UPDATE, getInitialCustomResponseParameters()); + } + + /* + * Delete states for the selected phase and job is not supported as per SIF3.x specification because states don't have a refID. + */ + @DELETE + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states{mimeType:(\\.[^/]*?)?}") + public Response removePhaseStates(String payload, @PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nDelete States (HTTP DELETE) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID +"\nURL Postfix mimeType: " + mimeType + "\nPayload:\n" + payload); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "The States of a Phase cannot be deleted."), ResponseAction.DELETE, getInitialCustomResponseParameters()); + } + + /* + * Delete a state for the selected phase and job is not supported as per SIF3.x specification because states don't have a refID. + */ + @DELETE + @Path("{jobID:([^\\.]*)}/{phaseName:([^\\.]*)}/states/{stateID:([^\\.]*)}{mimeType:(\\.[^/]*?)?}") + public Response removePhaseState(@PathParam("jobID") String jobID, @PathParam("phaseName") String phaseName, @PathParam("stateID") String stateID, @PathParam("mimeType") String mimeType) + { + if (logger.isDebugEnabled()) + { + logger.debug("\nDelete a State (HTTP DELETE) for Phase: "+phaseName+ "\nFunctional Service: "+getJobNamePlural()+"\nJob ID: "+ jobID+"\nState ID: "+ stateID +"\nURL Postfix mimeType: " + mimeType); + } + + return makeErrorResponse(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "A State of a Phase cannot be deleted."), ResponseAction.DELETE, getInitialCustomResponseParameters()); + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + private String getJobNamePlural() + { + return jobNamePlural; + } + + private void setJobNamePlural(String jobNamePlural) + { + this.jobNamePlural = jobNamePlural; + } + + /* + * This method is a helper to determine what the actual access right is. If a provider is a direct provider an access right is the actual + * right of the consumer as set in the environment ACL. If the provider is in a brokered environment its right is the ACL in relation + * to the broker. In such a case the right is simply 'PROVIDE'. + */ + private AccessRight getRight(AccessRight directEnvRight) + { + // If we are in a brokered environment then the access right must be PROVIDE. In a DIRECT environment the access right must be QUERY. + return getProviderEnvironment().getEnvironmentType() == EnvironmentType.DIRECT ? directEnvRight : AccessRight.PROVIDE; + } + + private FunctionalServiceProvider getFSProvider() + { + if (fsProvider == null) // No provider known for this Object Type! This is an issue and needs to be logged. + { + logger.error("No Provider known for the object with the name: "+getJobNamePlural()); + } + return fsProvider; + } + + private void setFSProvider(FunctionalServiceProvider fsProvider) + { + this.fsProvider = fsProvider; + } + + private JobType getJobDataFromTemplate() throws PersistenceException + { + // First we get the job template + JobType job = getEnvironmentManager().getJobTemplate(getJobNamePlural()); + if (job == null) // not good. No such job exists in job template store + { + String errorMsg = "No Job Template with the name " + getJobNamePlural() + " exists. Needs to be configured in appropriate tables first."; + logger.error(errorMsg); + throw new PersistenceException(errorMsg); + } + + // If we get here all good. Populate initialisation parameters. + Calendar now = Calendar.getInstance(); + job.setCreated(now); + return job; + } + + private JobType createJobInternal(JobType payloadJob, ResponseParameters responseParam, boolean consumerRequested) throws SIFException + { + // Get the template for the job to be created and populate missing bits. + JobType jobFromTemplate = null; + try + { + jobFromTemplate = getJobDataFromTemplate(); + + // Set the initialisation values + jobFromTemplate.setInitialization(payloadJob.getInitialization()); + + //Check if we need to allocate a jobId. + jobFromTemplate.setId(getAdvisory() ? payloadJob.getId() : UUIDGenerator.getUUID()); + + jobFromTemplate.setState(JobStateType.NOTSTARTED); // Default to not started. + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), "No Job Template Data found for "+getJobNamePlural(), ex.getMessage(), "Provider")); + } + + // pass it to the specific provider class for additional processing. + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + throw new SIFException(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider")); + } + + RequestMetadata requestMetadata = getRequestMetadata(getSIF3SessionForRequest(), false); + try + { + jobFromTemplate = (JobType)provider.createJob(jobFromTemplate, getNotNullSIFZone(), getNotNullSIFContext(), requestMetadata, responseParam); + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to create Job.", "Failed to create Job for "+getJobNamePlural()+". Problem reported: "+ex.getMessage(), "Provider")); + } + + SIF3Session sif3Session = getSIF3SessionForRequest(); + + // Finally save it in the SIF3_JOB Table and SIF3_JOB_EVENT table + try + { + String envID = null; + if (!isBrokeredEnvironment()) // In a DIRECT environment we have access to the consumer envID. + { + envID = sif3Session.getEnvironmentID(); + } + + // Save the job data to the appropriate tables. + getProviderJobManager().saveNewJob(jobFromTemplate, getJobNamePlural(), getNotNullSIFZone(), getNotNullSIFContext(), envID, requestMetadata.getFingerprint(), consumerRequested); + + return jobFromTemplate; + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to create Job.", "Failed to create Job. Problem reported: "+ex.getMessage(), "Provider")); + } + } + + private boolean removeJobInternal(String jobID, ResponseParameters responseParam, boolean consumerRequested) throws SIFException + { + // pass it to the specific provider class for additional processing. + FunctionalServiceProvider provider = getFSProvider(); + if (provider == null) // error already logged but we must return an error response for the caller + { + throw new SIFException(new ErrorDetails(Status.SERVICE_UNAVAILABLE.getStatusCode(), "Missing Functional Service Provider", "No Provider for "+getJobNamePlural()+" available.", "Provider")); + } + + // We must pass it to provider regardless if framework knows about job or not. + boolean deletedInProvider = false; + try + { + deletedInProvider = provider.deleteJob(jobID, getNotNullSIFZone(), getNotNullSIFContext(), getRequestMetadata(getSIF3SessionForRequest(), false), responseParam); + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to remove Job.", "Failed to remove Job for "+getJobNamePlural()+" and JobID = "+jobID+". Problem reported: "+ex.getMessage(), "Provider")); + } + + // Job may no longer be available in provider but we may still hold it in the JOB table. If so, we must remove it and make + // appropriate entry in JOB_EVENT table. If the job doesn't exist there either we return false, otherwise we return true. + // Finally remove it in the SIF3_JOB Table and make entry in SIF3_JOB_EVENT table + boolean deletedInJobTable = false; + try + { + deletedInJobTable = getProviderJobManager().removeJob(jobID, consumerRequested); + + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to remove Job.", "Failed to remove Job with jobID = "+jobID+". Problem reported: "+ex.getMessage(), "Provider")); + } + + return deletedInProvider || deletedInJobTable; + } + + /* + * @param jobInfo The job data. MUST NOT BE NULL. + * @param phaseName The phase for which the permission check shall be performed. + * @param right The permission required. + * @param provider Can be null if checkForEndstate == false. + * @param fingerprint the fingerprint of the consumer. + * @param checkForEndstate TRUE => provider is required. It will check if the given job is in an end state. FALSE no end state check + * is performed. + * @param checkPhaseStatePermission Indicates if permissions on the phase itself must be checked (FALSE) or permission on the + * Phase State must be checked (TRUE) + * + * @return ErrorDetails not null then it holds the reason why permission check failed. + */ + private ErrorDetails checkPhasePermission(ExtendedJobInfo jobInfo, String phaseName, AccessRight right, FunctionalServiceProvider provider, String fingerprint, boolean checkForEndstate, boolean checkPhaseStatePermission) + { + ErrorDetails error = validateSession(false); // Check if authenticated + if (error != null) // Not allowed to access! + { + return error; + } + + if (checkForEndstate) + { + if (isJobInEndState(jobInfo.getDBJob().getCurrentState(), provider)) + { + return new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Job Phase cannot be modifed.", getJobNamePlural()+" with Job ID = "+jobInfo.getDBJob().getJobID()+" is in state " +jobInfo.getDBJob().getCurrentState()+ " and can no longer be modified (End State)", "Provider"); + } + } + + //Check if service is valid + SIF3Session session = getSIF3SessionForRequest(); + SIFZone zone = getNotNullSIFZone(); + SIFContext context = getNotNullSIFContext(); + ServiceInfo serviceInfo = session.getServiceInfoForService(zone, context, getJobNamePlural(), ServiceType.FUNCTIONAL); + if (serviceInfo == null) // Found no service for the given functional service name. + { + return new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Access Denied.", "Consumer is not authorized to access the functional service '"+getJobNamePlural()+"'.", "Provider side check."); + } + try + { + if (checkPhaseStatePermission) + { + if (!hasConsumerPhaseStatusRightForJob(jobInfo, getJobNamePlural(), phaseName, zone, context, fingerprint, right, AccessType.APPROVED)) + { + return new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Consumer is not authorized to issue the requested operation.", right.name()+ " access is not set to "+AccessType.APPROVED.name()+" for states of phase "+phaseName+" of the functional service "+getJobNamePlural()+" and the given zone ("+zone.getId()+"), context ("+context.getId()+") and Job ("+jobInfo.getDBJob().getJobID()+").", "Provider side check."); + } + } + else + { + if (!hasConsumerPhaseRightForJob(jobInfo, getJobNamePlural(), phaseName, zone, context, right, AccessType.APPROVED)) + { + return new ErrorDetails(Status.FORBIDDEN.getStatusCode(), "Consumer is not authorized to issue the requested operation.", right.name()+ " access is not set to "+AccessType.APPROVED.name()+" for phase "+phaseName+" of the functional service "+getJobNamePlural()+" and the given zone ("+zone.getId()+"), context ("+context.getId()+") and Job ("+jobInfo.getDBJob().getJobID()+").", "Provider side check."); + } + } + } + catch (ResourceNotFoundException ex) + { + return ex.getErrorDetails(); + } + + // If we get here then all checks are passed! + return null; + } + + /* + * This method checks if a consumer identified by serviceName, zone, context and fingerprint has the given access right for + * the phase status for the job given by its jobID. If it has then TRUE is returned otherwise FALSE is returned. Also if any + * errors occur at the lower level (e.g. DB) and the right cannot be retrieved then false is returned. + * + * @param jobInfo The job for which the access right shall be determined. MUST NOT BE NULL! + * @param serviceName The functional service for which the jobId is applicable. MUST NOT BE NULL! + * @param phaseName The phase for which the status rights shall be checked. MUST NOT BE NULL! + * @param zone The zone in which the job is applicable. MUST NOT BE NULL! + * @param context The context in which the job is applicable. MUST NOT BE NULL! + * @param fingerprint The fingerprint of the consumer making the request. Can be null. + * @param right The right to check. MUST NOT BE NULL! + * @param accessType The value of the right to check. MUST NOT BE NULL! + * + * @return TRUE: The consumer has the access requested. FALSE: Consumer has not the required access right. + * + * @throws ResourceNotFoundException The given jobID is not applicable for the given parameters. + */ + private boolean hasConsumerPhaseStatusRightForJob(ExtendedJobInfo jobInfo, String serviceName, String phaseName, SIFZone zone, SIFContext context, String fingerprint, AccessRight right, AccessType accessType) throws ResourceNotFoundException + { + if (isConsumerValid(jobInfo, serviceName, zone, context, fingerprint)) + { + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase == null) // no phase found => return false + { + return false; + } + + return hasRight( phase.getStatesRights(), right, accessType); + } + else + { + throw new ResourceNotFoundException("Job does not exist.", "No Job with ID = "+jobInfo.getDBJob().getJobID()+" found for service = "+serviceName+" in zone = "+zone.getId()+" and context = "+context.getId()+". Fingerprint is "+fingerprint+".", "Provider"); + } + } + + /* + * This method checks if a consumer identified by serviceName, zone and context has the given access right for + * the phase for the job given by its jobID. If it has then TRUE is returned otherwise FALSE is returned. Also if any + * errors occur at the lower level (e.g. DB) and the right cannot be retrieved then false is returned. + * + * @param jobInfo The job for which the access right shall be determined. MUST NOT BE NULL! + * @param serviceName The functional service for which the jobId is applicable. MUST NOT BE NULL! + * @param phaseName The phase for which the status rights shall be checked. MUST NOT BE NULL! + * @param zone The zone in which the job is applicable. MUST NOT BE NULL! + * @param context The context in which the job is applicable. MUST NOT BE NULL! + * @param right The right to check. MUST NOT BE NULL! + * @param accessType The value of the right to check. MUST NOT BE NULL! + * + * @return TRUE: The consumer has the access requested. FALSE: Consumer has not the required access right. + * + * @throws ResourceNotFoundException The given jobID is not applicable for the given parameters. + */ + private boolean hasConsumerPhaseRightForJob(ExtendedJobInfo jobInfo, String serviceName, String phaseName, SIFZone zone, SIFContext context, AccessRight right, AccessType accessType) throws ResourceNotFoundException + { + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase == null) // no phase found => return false + { + return false; + } + + return hasRight(phase.getRights(), right, accessType); + } + + /* + * This method adds the state to a job phase state. This method can be called by the provider or a consumer through the appropriate + * service end-point (POST .../{serviceName}/{jobId}/{phaseName}/states/state). In case where the update is requested by a consumer it + * is expected that any tests weather the job is valid for the given consumer is already done. This method will only use the jobID to + * determine which job to update. If the job or phase doesn't exist then no action is taken. Again it is expected that appropriate + * tests are already performed. If events are enabled it will also add the appropriate job event to the event table. + * The following properties will be defaulted to: + * - Created: Current Date & Time + * - LastModifed: Current Date & Time + * - ID: If it was null then it will be set to a UUID. + * After a successful call to this method the DBJob and XML Job in the jobInfo hold the latest values (eg. state) + * + * @param jobInfo The job to be updated. Cannot be null. + * @param phaseName The name of the phase for which the state shall be set. Cannot be null + * @param newState The new state of the phase. + * + * @return The state created. This may have additional properties set (see default values in description). If null is returned then + * the state could not be added. + * + * @throws SIFException Job can not be accessed or updated due to a DB access issue. + */ + private StateType addPhaseStateByConsumer(ExtendedJobInfo jobInfo, String phaseName, StateType newState) throws SIFException + { + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase != null) // add the state + { + StateType state = addStateToPhase(phase, newState); + try + { + getProviderJobManager().updateJob(jobInfo, true); + } + catch (PersistenceException ex) + { + throw new SIFException(new ErrorDetails(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to update state to phase.", getJobNamePlural()+": Failed to add state to phase "+phaseName+" for and JobID = "+jobInfo.getDBJob().getJobID()+".", "Provider")); + } + + return state; + } + + return null; + } + + /* + * This method returns a State List for the given Job and Phase. This method can be called by the provider or a consumer through + * the appropriate service end-point (GET .../{serviceName}/{jobId}/{phaseName}/states). In case where the list is requested by a consumer + * it is expected that any tests weather the job is valid for the given consumer is already done. This method will only use the jobID to + * determine which job to use. If the job or phase doesn't exist then an empty list is returned. + * + * @param jobInfo The the Job to be retrieved. + * @param phaseName The name of the phase for which the status list shall be retrieved. + * + * @return See desc. + * + * @throws PersistenceException Job can not be accessed due to a DB access issue. + */ + private StateCollectionType getStatesOfPhase(ExtendedJobInfo jobInfo, String phaseName) throws PersistenceException + { + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase != null) // add the state + { + StateCollectionType states = phase.getStates(); + if (states == null) + { + states = new StateCollectionType(); + } + + return states; + } + + // If we get here then we haven't found an appropriate job of phase. + return new StateCollectionType(); + } + + private boolean isConsumerValid(ExtendedJobInfo job, String serviceName, SIFZone zone, SIFContext context, String fingerprint) + { + // Let'scheck if the fingerprint is given and if so it must match + boolean allGood = true; + if (StringUtils.notEmpty(fingerprint)) + { + allGood = allGood && fingerprint.equals(job.getDBJob().getFingerprint()); + } + + if (allGood) // lets check the rest: Is it the correct consumer in the right zone & context? + { + allGood = allGood && (serviceName.equals(job.getDBJob().getServiceName()) && zone.getId().equals(job.getDBJob().getZoneID()) && context.getId().equals(job.getDBJob().getContextID())); + } + + return allGood; + } + + private PhaseType getPhase(JobType job, String phaseName) + { + // Start iterating through the phases until it matches. + if (job.getPhases() == null) // no phases known + { + return null; + } + + for (PhaseType phase : job.getPhases().getPhase()) + { + if (phase.getName().equals(phaseName)) // found the applicable phase + { + return phase; + } + } + + // if we get here then we haven't found the correct phase + return null; + } + + private StateType addStateToPhase(PhaseType phase, StateType newState) + { + if (phase != null) // add the state + { + StateCollectionType states = phase.getStates(); + if (states == null) + { + states = new StateCollectionType(); + phase.setStates(states); + } + + StateType state = new StateType(); + state.setCreated(Calendar.getInstance()); + state.setLastModified(Calendar.getInstance()); + state.setId(StringUtils.isEmpty(newState.getId()) ? UUIDGenerator.getUUID() : newState.getId()); + state.setType(newState.getType()); + states.getState().add(state); + + return state; + } + + return null; + } + + private boolean hasRight(RightsType rights, AccessRight right, AccessType accessType) + { + if (rights == null) + { + return false; + } + + for (RightType rightType : rights.getRight()) + { + if (rightType.getType().equals(right.name()) && rightType.getValue().equals(accessType.name())) // Found match! + { + return true; + } + } + + // if we get here then we haven't found the correct right + return false; + } + + private ProviderJobManager getProviderJobManager() + { + return (ProviderJobManager)getEnvironmentManager().getJobManager(); + } + + private ExtendedJobInfo getFullJobInfo(String jobID, boolean xmlMustBeAvailable) throws SIFException, ResourceNotFoundException + { + try + { + ExtendedJobInfo jobInfo = getProviderJobManager().getJobInfo(jobID); + if (jobInfo != null) + { + if (xmlMustBeAvailable && !jobInfo.isXMLValid()) // we need the XML but it is invalid! + { + throw new SIFException(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve job.", getJobNamePlural()+": Failed to retrieve Job with ID = "+jobID+". The XML associated with the job is invalid.", "Provider"); + } + } + else + { + throw new ResourceNotFoundException("Job does not exist.", getJobNamePlural()+": Job with ID = "+jobID+" does not exist.", "Provider"); + } + + return jobInfo; + + } + catch (PersistenceException ex) + { + throw new SIFException(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Failed to retrieve job.", getJobNamePlural()+": Failed to retrieve Job with ID = "+jobID+". Error reported: "+ex.getMessage(), "Provider"); + } + } + + private boolean isJobInEndState(String state, FunctionalServiceProvider provider) + { + return provider.isJobEndState(JobState.valueOf(state)); + } + + private String getValidPhaseStates() + { + PhaseStateType[] states = PhaseStateType.values(); + String statesStr = ""; + for (PhaseStateType state : states) + { + statesStr = statesStr + ", "+state.value(); + } + + return statesStr.substring(2); + } +} diff --git a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/web/ProviderServletContext.java b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/web/ProviderServletContext.java index 953f949f..21ef55a0 100644 --- a/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/web/ProviderServletContext.java +++ b/SIF3InfraREST/SIF3REST/src/main/java/sif3/infra/rest/web/ProviderServletContext.java @@ -18,7 +18,11 @@ package sif3.infra.rest.web; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; import java.util.EnumSet; +import java.util.Enumeration; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration.Dynamic; @@ -35,10 +39,13 @@ import sif3.common.persist.common.HibernateHelper; import sif3.common.persist.common.HibernateUtil; import sif3.infra.common.env.mgr.ProviderManagerFactory; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.env.types.ProviderEnvironment; import sif3.infra.common.interfaces.EnvironmentConnector; import sif3.infra.common.interfaces.EnvironmentManager; import sif3.infra.rest.env.connectors.EnvironmentConnectorFactory; import sif3.infra.rest.provider.ProviderFactory; +import sif3.infra.rest.quarz.FunctionalServiceHouseKeeping; /** * This class is to initialise the provider at startup and clean up resources at shutdown. @@ -54,6 +61,7 @@ public class ProviderServletContext implements ServletContextListener private static final String SERVICE_PROPERTY_FILE_TAG = "SERVICE_PROPERTY_FILE"; private EnvironmentConnector connector = null; + private FunctionalServiceHouseKeeping fsHouseKeeper = null; /* * (non-Javadoc) @@ -127,6 +135,9 @@ public void contextInitialized(ServletContextEvent servletCtxEvent) // If all is good till now we try to install the Auditor Filter installAuditorFilter(servletCtxEvent, envMgr.getServiceProperties()); + + // Setup back ground processes or Cron jobs + scheduleJobs(envMgr.getEnvironmentInfo()); } logger.info("Initialise Provider sucessful: "+allOK); @@ -146,6 +157,12 @@ public void contextInitialized(ServletContextEvent servletCtxEvent) */ public void contextDestroyed(ServletContextEvent servletCtxEvent) { + logger.info("Shutdown Functional Services Housekeeper..."); + if (fsHouseKeeper != null) + { + fsHouseKeeper.shutdown(); + } + logger.info("Shutdown Provider..."); EnvironmentManager envMgr = ProviderManagerFactory.getEnvironmentManager(); @@ -160,6 +177,10 @@ public void contextDestroyed(ServletContextEvent servletCtxEvent) logger.debug("Release DB Connections...."); HibernateUtil.shutdown(); + + logger.debug("Unregister JDBC drivers that may have been used ...."); + unregisterJDBCDrivers(); + logger.info("Shutdown Provider: done."); } @@ -194,4 +215,53 @@ private void installAuditorFilter(ServletContextEvent servletCtxEvent, AdvancedP } } + private void scheduleJobs(EnvironmentInfo envInfo) + { + ProviderEnvironment providerEnvironment = (ProviderEnvironment)envInfo; + if (providerEnvironment.isJobEnabled()) + { + logger.info("Install Functional Service HouseKeeping Job."); + fsHouseKeeper = new FunctionalServiceHouseKeeping(); + if (!fsHouseKeeper.start(providerEnvironment)) + { + logger.error("Failed to schedule Functional Service House Keeper. See previous error log entry for details."); + } + } + else + { + logger.info("Functional Services not enabled for this Provider."); + } + } + + + private void unregisterJDBCDrivers() + { + // Now de-register JDBC drivers in this context's ClassLoader: Get the webapp's ClassLoader + ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); + + // Loop through all drivers + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) + { + Driver driver = drivers.nextElement(); + if (driver.getClass().getClassLoader() == webAppClassLoader) + { + // This driver was registered by the webapp's ClassLoader, so deregister it: + try + { + logger.info("Deregistering JDBC driver {}", driver); + DriverManager.deregisterDriver(driver); + } + catch (SQLException ex) + { + logger.error("Error deregistering JDBC driver {}", driver, ex); + } + } + else + { + // driver was not registered by the webapp's ClassLoader and may be in use elsewhere + logger.trace( "Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader", driver); + } + } + } } diff --git a/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/client/TestJobClient.java b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/client/TestJobClient.java new file mode 100644 index 00000000..bce3cafb --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/client/TestJobClient.java @@ -0,0 +1,522 @@ +/* + * TestJobClient.java + * Created: 22 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.test.client; + +import java.util.ArrayList; + +import javax.ws.rs.core.MediaType; + +import au.com.systemic.framework.utils.FileReaderWriter; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.header.HeaderProperties; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.model.AttributeValue; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFZone; +import sif3.common.model.URLQueryParameter; +import sif3.common.model.job.JobCreateRequestParameter; +import sif3.common.model.job.PhaseInfo; +import sif3.common.utils.UUIDGenerator; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.env.mgr.ConsumerEnvironmentManager; +import sif3.infra.rest.client.JobClient; +import sif3.infra.rest.consumer.ConsumerLoader; + +/** + * @author Joerg Huber + * + */ +public class TestJobClient +{ + // Local + private static final String CONSUMER_ID = "StudentConsumer"; + private static final String JOB_NAME_SINGULAR = "RolloverStudent"; + private static final String JOB_NAME_PLURAL = JOB_NAME_SINGULAR + "s"; + + private static final String BASE_PATH = "/Development/GitHubRepositories/SIF3InfraRest/SIF3InfraREST/TestData/xml/input"; + private static final String CREATE_PHASE_PAYLOAD = BASE_PATH+"/CreatePhasePayload.xml"; + private static final String UPDATE_PHASE_PAYLOAD = BASE_PATH+"/CreatePhasePayload.xml"; + private static final String DELETE_PHASE_PAYLOAD = BASE_PATH+"/DeletePhasePayload.xml"; + + private static final String JOB_ID = "74f2ebfb-9cf8-4aa2-b757-12237d6c3c02"; + + // Broker + //private static final String CONSUMER_ID = "BrokeredAttTrackerConsumer"; + + private JobClient client = null; + + private String getPhasePayload(String payloadFile) + { + return new String(FileReaderWriter.getFileBytes(payloadFile)); + } + + private void initClient() + { + client = new JobClient(ConsumerEnvironmentManager.getInstance(), JOB_NAME_PLURAL, JOB_NAME_SINGULAR); + } + + private void testGetJob() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTH_TOKEN, AUTH_TOKEN); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.getJob("82feecf1-2f01-40f8-8ece-b4a1d030a6ef", hdrProps, urlQueryParams, null, null); + + if (response.hasError()) + { + System.out.println("Error Occured retrieving Job."); + } + else + { + System.out.println("Retrieving Job succeeded."); + } + System.out.println(response); + if (!response.hasError()) + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(marshaller.marshalToXML(response.getDataObject())); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testGetJobs() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTH_TOKEN, AUTH_TOKEN); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.getJobs(new PagingInfo(5, 2), hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); +// Response response = client.getJobs(new PagingInfo(5, 1), hdrProps, urlQueryParams, new SIFZone("TestZone"), null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured retrieving Jobx."); + } + else + { + System.out.println("Retrieving Jobs succeeded."); + } + System.out.println(response); + if (!response.hasError()) + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(marshaller.marshalToXML(response.getDataObject())); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testGetJobPhaseStates(String phaseName) + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.getPhaseStates(new PhaseInfo(JOB_ID, phaseName), hdrProps, urlQueryParams, null, null); + + if (response.hasError()) + { + System.out.println("Error Occured Retrieving Job Phase States."); + } + else + { + System.out.println("Job Phase State retrieved succeessfully."); + } + System.out.println(response); + if (!response.hasError()) + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(marshaller.marshalToXML(response.getDataObject())); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + + private void testCreateJob() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTH_TOKEN, AUTH_TOKEN); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + +// ArrayList attrValue = new ArrayList(); +// attrValue.add(new AttributeValue("old-year", "2017")); +// attrValue.add(new AttributeValue("new-year", "2018")); + JobCreateRequestParameter jobCreateInfo = new JobCreateRequestParameter("oldYearEnrolment", null); + Response response = client.createJob(jobCreateInfo, hdrProps, urlQueryParams, null, null); + + System.out.println("Job ID assigned = "+jobCreateInfo.getJobID()); + + if (response.hasError()) + { + System.out.println("Error Occured Job Create Request."); + } + else + { + System.out.println("Create Job succeeded."); + } + System.out.println(response); + if (!response.hasError()) + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(marshaller.marshalToXML(response.getDataObject())); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testUpdateJobPhaseState(String phaseName, PhaseState newState) + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.updatePhaseState(new PhaseInfo(JOB_ID, phaseName), newState, hdrProps, urlQueryParams, null, null); + + if (response.hasError()) + { + System.out.println("Error Occured Updateing Job Phase State Request."); + } + else + { + System.out.println("Job Phase State Updated succeeded."); + } + System.out.println(response); + if (!response.hasError()) + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(marshaller.marshalToXML(response.getDataObject())); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + + private void testCreateJobs() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); +// hdrProps.setHeaderProperty(RequestHeaderConstants.HDR_AUTH_TOKEN, AUTH_TOKEN); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + ArrayList jobInits = new ArrayList(); + jobInits.add(new JobCreateRequestParameter("oldYearEnrolment", null)); + jobInits.add(new JobCreateRequestParameter("newYearEnrolment", null)); + jobInits.add(new JobCreateRequestParameter(null, null)); + jobInits.add(null); + + BulkOperationResponse response = client.createJobs(jobInits, hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Job Create Request."); + } + else + { + System.out.println("Create Job succeeded."); + } + System.out.println(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testRemoveJob() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.removeJob("a1b314d1-7cbe-4bce-a5d8-cb115b4f03cb", hdrProps, urlQueryParams, null, null); + + if (response.hasError()) + { + System.out.println("Error Occured Job Delete Request."); + } + else + { + System.out.println("Delete Job succeeded."); + } + System.out.println(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + + private void testRemoveJobs() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + ArrayList jobIDs = new ArrayList(); + jobIDs.add("80c6b1ca-735a-40f5-8ef0-99908add342a"); + jobIDs.add("3ddbf879-f43f-4438-843f-b224fb5ef172"); + jobIDs.add("9c26cc3a-bd99-471f-88b5-898b0cd2e4d6"); + jobIDs.add("82feecf1-2f01-40f8-8ece-b4a1d030a6ef"); + + BulkOperationResponse response = client.removeJobs(jobIDs, hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Jobs Delete Request."); + } + else + { + System.out.println("Delete Job succeeded."); + } + System.out.println(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testGetServiceInfo() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.getServiceInfo(null, hdrProps, urlQueryParams, null, null); + + if (response.hasError()) + { + System.out.println("Error Occured retrieving Job."); + } + else + { + System.out.println("Retrieving Service Info succeeded."); + } + System.out.println(response); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testRetriveFromPhase() + { + try + { + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.retrieveDataFromPhase(new PhaseInfo(JOB_ID, "oldYearEnrolment"), MediaType.APPLICATION_XML_TYPE, new PagingInfo(5, 1), hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Retrieving Job Phase Data."); + } + else + { + System.out.println("Job Phase Data retrieved succeessfully."); + } + System.out.println(response); + if (!response.hasError()) + { + System.out.println("Data Type of Response: "+response.getDataObjectType()); + System.out.println("Data Retrieved: "+response.getDataObject()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testCreateDataInPhase() + { + try + { + String payload = getPhasePayload(CREATE_PHASE_PAYLOAD); + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.createDataInPhase(new PhaseInfo(JOB_ID, "oldYearEnrolment"), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, true, hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Creating Job Phase Data."); + } + else + { + System.out.println("Job Phase Data created succeessfully."); + } + System.out.println(response); + if (!response.hasError()) + { + System.out.println("Data Type of Response: "+response.getDataObjectType()); + System.out.println("Data Retrieved: "+response.getDataObject()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testUpdateDataInPhase() + { + try + { + String payload = getPhasePayload(UPDATE_PHASE_PAYLOAD); + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.updateDataInPhase(new PhaseInfo(JOB_ID, "oldYearEnrolment"), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Updating Job Phase Data."); + } + else + { + System.out.println("Job Phase Data update succeessfully."); + } + System.out.println(response); + if (!response.hasError()) + { + System.out.println("Data Type of Response: "+response.getDataObjectType()); + System.out.println("Data Retrieved: "+response.getDataObject()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void testDeleteDataInPhase() + { + try + { + String payload = getPhasePayload(DELETE_PHASE_PAYLOAD); + HeaderProperties hdrProps = new HeaderProperties(); + URLQueryParameter urlQueryParams = new URLQueryParameter(); + + Response response = client.deleteDataInPhase(new PhaseInfo(JOB_ID, "oldYearEnrolment"), new PhaseDataRequest(payload, MediaType.APPLICATION_XML_TYPE), MediaType.APPLICATION_XML_TYPE, hdrProps, urlQueryParams, null, null, RequestType.IMMEDIATE); + + if (response.hasError()) + { + System.out.println("Error Occured Deleting Job Phase Data."); + } + else + { + System.out.println("Job Phase Data delete succeessfully."); + } + System.out.println(response); + if (!response.hasError()) + { + System.out.println("Data Type of Response: "+response.getDataObjectType()); + System.out.println("Data Retrieved: "+response.getDataObject()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + + } + + public static void main(String[] args) + { + TestJobClient tester = new TestJobClient(); + + if (ConsumerLoader.initialise(CONSUMER_ID)) + { + System.out.println("Start Testing JobClient..."); + tester.initClient(); + + // + // Job Operations + // +// tester.testGetJob(); +// tester.testGetJobs(); + +// tester.testCreateJob(); +// tester.testCreateJobs(); + + tester.testRemoveJob(); +// tester.testRemoveJobs(); +// tester.testGetServiceInfo(); + + // + // Phase State Operations + // +// tester.testUpdateJobPhaseState("newYearEnrolment", PhaseState.FAILED); +// tester.testGetJobPhaseStates("newYearEnrolment"); + + // + // Phase Operations + // +// tester.testRetriveFromPhase(); +// tester.testCreateDataInPhase(); +// tester.testUpdateDataInPhase(); +// tester.testDeleteDataInPhase(); + + System.out.println("End Testing JobClient."); + + ConsumerLoader.shutdown(); + } + else + { + System.out.println("Failed to initialse tester!"); + } + } +} diff --git a/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/JobHouseKeeping.java b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/JobHouseKeeping.java new file mode 100644 index 00000000..e1fa00ad --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/JobHouseKeeping.java @@ -0,0 +1,144 @@ +/* + * JobHouseKeeping.java + * Created: 21 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.test.quartz; + +import java.util.Date; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.TriggerBuilder; +import org.quartz.impl.StdSchedulerFactory; + +import sif3.infra.common.env.types.ProviderEnvironment; + +/** + * @author Joerg Huber + * + */ +public class JobHouseKeeping implements Job +{ +// private String message = null; + private Scheduler scheduler = null; + +// public JobHouseKeeping(String message) +// { +// try +// { +// JobDetail job = JobBuilder.newJob(JobHouseKeeping.class).withIdentity("job1", "group1").build(); +// +// CronTrigger trigger = TriggerBuilder.newTrigger() +// .withIdentity("trigger1", "group1") +// .withSchedule(CronScheduleBuilder.cronSchedule("0/20 * * * * ?")) +// .build(); +// +// setScheduler(new StdSchedulerFactory().getScheduler()); +// getScheduler().start(); +// getScheduler().scheduleJob(job, trigger); +// +// } +// catch (Exception ex) +// { +// ex.printStackTrace(); +// } +// } + + public void start(ProviderEnvironment env) + { +// setMessage(message); + try + { + JobDetail job = JobBuilder.newJob(JobHouseKeeping.class).withIdentity("job1", "group1").build(); + JobDataMap dataMap = job.getJobDataMap(); + dataMap.put("ENV_DATA", env); + + CronTrigger trigger = TriggerBuilder.newTrigger() + .withIdentity("trigger1", "group1") + .withSchedule(CronScheduleBuilder.cronSchedule("0/20 * * * * ?")) + .build(); + + setScheduler(new StdSchedulerFactory().getScheduler()); + getScheduler().start(); + getScheduler().scheduleJob(job, trigger); + + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + public void shutdown() + { + System.out.println("Shutting down JobHouseKeeping..."); + try + { + if (scheduler != null) + { + scheduler.shutdown(true); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } + + /* (non-Javadoc) + * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) + */ + @Override + public void execute(JobExecutionContext jobctx) throws JobExecutionException + { + JobKey jobKey = jobctx.getJobDetail().getKey(); + ProviderEnvironment env = (ProviderEnvironment)jobctx.getJobDetail().getJobDataMap().get("ENV_DATA"); + + System.out.println("JobHouseKeeping says: " + jobKey + " executing at " + new Date()+". Number of Threads: "+ Thread.activeCount()); + System.out.println("Environment Data Available: " + env); + } + + +// public String getMessage() +// { +// return message; +// } +// +// public void setMessage(String message) +// { +// this.message = message; +// } + + public Scheduler getScheduler() + { + return scheduler; + } + + public void setScheduler(Scheduler scheduler) + { + this.scheduler = scheduler; + } + + +} diff --git a/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/SchedullerTest.java b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/SchedullerTest.java new file mode 100644 index 00000000..398a4706 --- /dev/null +++ b/SIF3InfraREST/SIF3REST/src/test/java/sif3/infra/rest/test/quartz/SchedullerTest.java @@ -0,0 +1,61 @@ +/* + * SchedullerTest.java + * Created: 21 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.rest.test.quartz; + +import sif3.infra.common.env.types.AdapterEnvironmentStore; +import sif3.infra.common.env.types.ProviderEnvironment; + +/** + * @author Joerg Huber + * + */ +public class SchedullerTest +{ + private static final String PROP_FILE_NAME = "StudentProvider"; + + public static void main(String[] args) + { + + try + { + AdapterEnvironmentStore envStore = new AdapterEnvironmentStore(PROP_FILE_NAME); + JobHouseKeeping houseKeeper = new JobHouseKeeping(); + houseKeeper.start((ProviderEnvironment)envStore.getEnvironment()); + Thread.sleep(60L * 1000L); // run for one minute + houseKeeper.shutdown(); +// JobDetail job = JobBuilder.newJob(JobHouseKeeping.class).withIdentity("job1", "group1").build(); +// +// CronTrigger trigger = TriggerBuilder.newTrigger() +// .withIdentity("trigger1", "group1") +// .withSchedule(CronScheduleBuilder.cronSchedule("0/20 * * * * ?")) +// .build(); +// +// Scheduler scheduler = new StdSchedulerFactory().getScheduler(); +// scheduler.start(); +// scheduler.scheduleJob(job, trigger); +// +// Thread.sleep(60L * 1000L); // run for one minute +// +// scheduler.shutdown(true); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + } +} diff --git a/SIF3InfraREST/TestData/xml/input/CreatePhasePayload.xml b/SIF3InfraREST/TestData/xml/input/CreatePhasePayload.xml new file mode 100644 index 00000000..8ddd7a46 --- /dev/null +++ b/SIF3InfraREST/TestData/xml/input/CreatePhasePayload.xml @@ -0,0 +1,30 @@ + + + 98765 + + + DAVEY + Tim + + + + + + + + + 98765 + + + DAVEY + Tim + + + + + + + + diff --git a/SIF3InfraREST/TestData/xml/input/DeletePhasePayload.xml b/SIF3InfraREST/TestData/xml/input/DeletePhasePayload.xml new file mode 100644 index 00000000..655f1893 --- /dev/null +++ b/SIF3InfraREST/TestData/xml/input/DeletePhasePayload.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/SIF3InfraREST/TestData/xml/input/StudentPersonals5.xml b/SIF3InfraREST/TestData/xml/input/StudentPersonals5.xml index 56d06963..e2b0af56 100644 --- a/SIF3InfraREST/TestData/xml/input/StudentPersonals5.xml +++ b/SIF3InfraREST/TestData/xml/input/StudentPersonals5.xml @@ -3,8 +3,8 @@ 98765 - DAVEY - Tim + Huber + JŒRG @@ -16,8 +16,8 @@ 98765 - DAVEY - Tim + Marshall + Linda @@ -55,8 +55,8 @@ - - + O¿Connor + Sam @@ -64,4 +64,4 @@ - + \ No newline at end of file diff --git a/SIF3InfraREST/TestData/xml/input/rolloverStudentJob.xml b/SIF3InfraREST/TestData/xml/input/rolloverStudentJob.xml new file mode 100644 index 00000000..1799eb12 --- /dev/null +++ b/SIF3InfraREST/TestData/xml/input/rolloverStudentJob.xml @@ -0,0 +1,64 @@ + + rolloverStudent + Rollover Student from year X to year Y + NOTSTARTED + Not Started + + + P3D + + + oldYearEnrolment + + + NOTSTARTED + + + + true + + APPROVED + + + APPROVED + APPROVED + + + + newYearEnrolment + + + NOTSTARTED + + + + true + + APPROVED + + + APPROVED + APPROVED + + + + + initial-parameters + + + future + user1 + vendora + 3pi + 2018 + + + \ No newline at end of file diff --git a/SIF3InfraREST/TestData/xml/output/.gitignore b/SIF3InfraREST/TestData/xml/output/.gitignore index 9b5966d8..687b6eb2 100644 --- a/SIF3InfraREST/TestData/xml/output/.gitignore +++ b/SIF3InfraREST/TestData/xml/output/.gitignore @@ -2,3 +2,5 @@ /StudentDailyAttendanceConsumer 2.log /StudentPersonalConsumer 1.log /StudentPersonalConsumer 2.log +/RolloverStudentConsumer 1.log +/RolloverStudentConsumer 2.log diff --git a/SIF3InfraREST/documentation/UseCases/HITS/SIF3 Framework Consumer and HITS.docx b/SIF3InfraREST/documentation/UseCases/HITS/SIF3 Framework Consumer and HITS.docx index 12c54271..c368e342 100644 Binary files a/SIF3InfraREST/documentation/UseCases/HITS/SIF3 Framework Consumer and HITS.docx and b/SIF3InfraREST/documentation/UseCases/HITS/SIF3 Framework Consumer and HITS.docx differ diff --git a/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.7.2.docx b/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.7.2.docx deleted file mode 100644 index 8031be29..00000000 Binary files a/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.7.2.docx and /dev/null differ diff --git a/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.8.0.docx b/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.8.0.docx new file mode 100644 index 00000000..482891f6 Binary files /dev/null and b/SIF3InfraREST/documentation/UserGuide/SIF3Framework_DevelopersGuide_v0.8.0.docx differ diff --git a/SIF3InfraREST/documentation/javadoc.zip b/SIF3InfraREST/documentation/javadoc.zip index e876bd7d..1e164822 100644 Binary files a/SIF3InfraREST/documentation/javadoc.zip and b/SIF3InfraREST/documentation/javadoc.zip differ diff --git a/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.1.jar b/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.1.jar deleted file mode 100644 index 4fef4925..00000000 Binary files a/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.1.jar and /dev/null differ diff --git a/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.3.jar b/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.3.jar new file mode 100644 index 00000000..714b153d Binary files /dev/null and b/SIF3InfraREST/lib/datamodel/sifDataModel_au3.4.3.jar differ diff --git a/SIF3InfraREST/pom.xml b/SIF3InfraREST/pom.xml index e3a6cb2f..aca1087f 100644 --- a/SIF3InfraREST/pom.xml +++ b/SIF3InfraREST/pom.xml @@ -17,7 +17,7 @@ sif3.framework sif3-framework - 0.12.0-beta + 0.13.0-beta pom @@ -61,7 +61,7 @@ sifau sif3-au-datamodel - 3.4.1 + 3.4.3 test @@ -74,12 +74,14 @@ 4.0 test
+ jdbc oracle-jdbc 6.0 test + jdbc sqlite-jdbc @@ -104,6 +106,15 @@ test + + + + + org.quartz-scheduler + quartz + 2.3.0 + + @@ -155,11 +166,11 @@ install-external-au-datamodel clean - ${project.lib.dir}/datamodel/sifDataModel_au3.4.1.jar + ${project.lib.dir}/datamodel/sifDataModel_au3.4.3.jar default sifau sif3-au-datamodel - 3.4.1 + 3.4.3 jar true @@ -233,10 +244,10 @@ - SIF3Common - SIF3InfraModel - SIF3InfraCommon - SIF3REST + sif3Common + sif3InfraModel + sif3InfraCommon + sif3REST SIF3 Framework Parent Project This is the parent project to build all SIF3 Framework Artifacts. diff --git a/SIF3InfraREST/release/sif3.2.1Common-0.12.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Common-0.13.0-beta.jar similarity index 60% rename from SIF3InfraREST/release/sif3.2.1Common-0.12.0-beta.jar rename to SIF3InfraREST/release/sif3.2.1Common-0.13.0-beta.jar index fa50d286..1553970b 100644 Binary files a/SIF3InfraREST/release/sif3.2.1Common-0.12.0-beta.jar and b/SIF3InfraREST/release/sif3.2.1Common-0.13.0-beta.jar differ diff --git a/SIF3InfraREST/release/sif3.2.1Infra-common-0.12.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Infra-common-0.12.0-beta.jar deleted file mode 100644 index cdb32113..00000000 Binary files a/SIF3InfraREST/release/sif3.2.1Infra-common-0.12.0-beta.jar and /dev/null differ diff --git a/SIF3InfraREST/release/sif3.2.1Infra-common-0.13.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Infra-common-0.13.0-beta.jar new file mode 100644 index 00000000..b2cc7669 Binary files /dev/null and b/SIF3InfraREST/release/sif3.2.1Infra-common-0.13.0-beta.jar differ diff --git a/SIF3InfraREST/release/sif3.2.1Infra-model-0.12.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Infra-model-0.13.0-beta.jar similarity index 84% rename from SIF3InfraREST/release/sif3.2.1Infra-model-0.12.0-beta.jar rename to SIF3InfraREST/release/sif3.2.1Infra-model-0.13.0-beta.jar index 172f59cf..c4e522ad 100644 Binary files a/SIF3InfraREST/release/sif3.2.1Infra-model-0.12.0-beta.jar and b/SIF3InfraREST/release/sif3.2.1Infra-model-0.13.0-beta.jar differ diff --git a/SIF3InfraREST/release/sif3.2.1Infra-rest-0.12.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Infra-rest-0.12.0-beta.jar deleted file mode 100644 index 1f8d7dfc..00000000 Binary files a/SIF3InfraREST/release/sif3.2.1Infra-rest-0.12.0-beta.jar and /dev/null differ diff --git a/SIF3InfraREST/release/sif3.2.1Infra-rest-0.13.0-beta.jar b/SIF3InfraREST/release/sif3.2.1Infra-rest-0.13.0-beta.jar new file mode 100644 index 00000000..63d52567 Binary files /dev/null and b/SIF3InfraREST/release/sif3.2.1Infra-rest-0.13.0-beta.jar differ diff --git a/SIF3InfraREST/release/v0.13.0/Release_Notes_v0.13.0.txt b/SIF3InfraREST/release/v0.13.0/Release_Notes_v0.13.0.txt new file mode 100644 index 00000000..3a05cc0f --- /dev/null +++ b/SIF3InfraREST/release/v0.13.0/Release_Notes_v0.13.0.txt @@ -0,0 +1,99 @@ +============================================================== +Release Notes for v0.13.0 of SIF3 Framework (Sept 25, 2018) +============================================================== + +Bug Fixes +--------- +- Fixed an issue with the DateUtils class where ISO 8601 Dates didn't convert correctly to Zulu (GMT) Date and Time. +- Fixed issue where new 'changesSinceMarker' wasn't returned in the changes since request. +- Fixed GitHub issue #34 - Provider in Brokered Environment over eager properties validation. +- Fixed GitHub issue #35 - Improve Queue Reader Exception Handling +- Fixed GitHub issue #36 - Typo: 'SIF' in lower case in main pom.xml +- Fixed GitHub issue #37 - Remote message readers not stopping +- Fixed GitHub issue #38 - Delayed error responses not handled correctly + +New Functionality +----------------- +- Functional Services (Usage of this functionality is detailed in the developer's guide in section 5.11): + - Provider Side. This includes all CRUD operations for Job, Phases and Status. Also supported out-of-the box is "ChangesSince" + and publishing of events. + - Consumer Side: This includes all CRUD operations for Job, Phases and Status. Also supported out-of-the box is "ChangesSince". + and event consuming. + +Changed Functionality +--------------------- +- Added SIFException to many Provider Interface methods to enable providers to throw a "generic" exceptions where the provider can customise + the HTTP Status Code and error message to be returned to the consumer. This enables implementers of providers to easy create custom error + messages that are not defaulted or covered by the framework. + This change may break some or all provider classes (compiler error). To rectify the issue simply add the SIFException to the list of + exceptions defined by appropriate provider class method. For example if the implementations uses service paths and therefore must implement + retrieveByServicePath() method you would change the method signature in your implementation class + from: + public Object retrieveByServicePath(...) throws PersistenceException, UnsupportedQueryException, DataTooLargeException + { + } + + to (note the additional SIFException in the 'throws' clause): + public Object retrieveByServicePath(...) throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException + { + } + + In many cases no errors are shown though because adding an exception to an interface class will not break implementations immediately. However + if your implementation wants to make use of the new SIFException it must be added in the signature of the appropriate method as listed in the + corresponding interface class. The change would be the same as listed above. + + Affected interfaces are: Provider, ChangesSinceProvider, QueryProvider + +- A number of exceptions have changed to extend the new SIFException. This should not break your code as special care has been taken to keep + the standard exception functionality. However if something should break (eg. not compile) then it is suggested to check the appropriate + exception's documentation. + The motivation for that change is the streamlining of the exception handling on the providers since many of these exceptions need to be + translated into SIF Error Messages and then be returned to the consumer. + + The following exceptions do now extend SIFException (note not all of them were available in previous versions of the framework): + - UnsupportedQueryException + - ExpiredException (NEW: used in Changes Since method) + - ResourceNotFoundException (NEW: currently only used internally but may be available in future for some methods) + - DataTooLargeException + +Removed Artifacts/Functionality +------------------------------- +- None + + +========================== +-- Upgrade Instructions -- +========================== + +Infrastructure Upgrade +---------------------- +With the addition of the Functional Services capabilities additional tables need to be created in the infrastructure schema/database. +The script can be found in the directory /DB/DDL/Datafix/current/v0.12.0-v0.13.0. Run the script +with the name Datafix_Job_.sql. If you use a database other than the ones that have an update script provided you should be +able to derive your script based on one of the provided one's. + +Upgrade your Project +-------------------- +Once the above steps are done you can upgrade your project with the latest libraries of the framework as stated below: + +Ant: +Drop the latest framework library into your project's lib directory/directories. Also take note of the '3rd Party Library +Update' section below. Quartz v2.3.0 is used and has dependencies on other 3rd party library. It might be necessary that +you need to add additional libraries to your project to resolve such dependencies. The dependencies can be found on this +site: https://mvnrepository.com/artifact/org.quartz-scheduler/quartz/2.3.0 + +Maven: +Use the latest maven dependency + + sif3.framework + sif3-infra-rest + 0.13.0 + + +Data Model Upgrade +------------------ +None. + +3rd Party Library Update +------------------------ +The framework has a new dependency on Quartz. This has been added accordingly to the appropriate pom.xml. diff --git a/SIF3InfraREST/sif3Common/pom.xml b/SIF3InfraREST/sif3Common/pom.xml index 746f86aa..b0972079 100644 --- a/SIF3InfraREST/sif3Common/pom.xml +++ b/SIF3InfraREST/sif3Common/pom.xml @@ -6,7 +6,7 @@ sif3.framework sif3-framework - 0.12.0-beta + 0.13.0-beta diff --git a/SIF3InfraREST/sif3Common/src/main/java/au/com/systemic/framework/utils/DateUtils.java b/SIF3InfraREST/sif3Common/src/main/java/au/com/systemic/framework/utils/DateUtils.java index ba8dab81..1d907f1b 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/au/com/systemic/framework/utils/DateUtils.java +++ b/SIF3InfraREST/sif3Common/src/main/java/au/com/systemic/framework/utils/DateUtils.java @@ -24,6 +24,11 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; +import java.util.TimeZone; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.Duration; /** * @author Joerg Huber @@ -67,12 +72,6 @@ public class DateUtils public static final SimpleDateFormat DISP_TIME_SEC = new SimpleDateFormat("HH:mm:ss"); public static final SimpleDateFormat DISP_TIME_NO_SEC = new SimpleDateFormat("HH:mm"); - /* - * ISO Standards - */ - public static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - public static final SimpleDateFormat ISO_8601_SECFRACT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - public static final String DATE_FORMAT_dd_SLASH_mm_SLASH_yyyy = "dd/MM/yyyy"; public static final String DATE_FORMAT_dd_DOT_mm_DOT_yyyy = "dd.MM.yyyy"; public static final String DATE_FORMAT_mm_SLAST_dd_SLASH_yyyy = "MM/dd/yyyy"; @@ -81,6 +80,21 @@ public class DateUtils public static final String DATE_FORMAT_yyyy_mm_dd = "yyyy-MM-dd"; public static final String DATE_FORMAT_yyyy_mm_dd_HH_mm = "yyyy-MM-dd HH:mm"; + /* + * ISO Standards + */ + public static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + public static final SimpleDateFormat ISO_8601_SECFRACT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + public static final String ZULU_TIMEZONE = "Z"; + + // Ensure that the ISO 8601 Formats use GMT/Zulu timezone! + static + { + ISO_8601.setTimeZone(TimeZone.getTimeZone(ZULU_TIMEZONE)); + ISO_8601_SECFRACT.setTimeZone(TimeZone.getTimeZone(ZULU_TIMEZONE)); + } + /** * Transforms the given string into a date. The string must follow the * format as presented by the given format parameter. If the string has another @@ -368,6 +382,54 @@ public static Date convertUnixTstampDate(long gmtTstamp) return new Date(gmtTstamp * 1000); } + /** + * This method adds the duration given by the 'durationStr' to the given 'fromDate'. The fromDate is not altered instead the final + * result is returned. Note that the 'durationStr' must follow the syntax as defined in javax.xml.datatype.Duration. If the durationStr + * is invalid a ParseException is thrown. + * + * @param durationStr if null then the 'fromDate' is returned. + * @param fromDate Can be null. In this case the 'current date' is used. + * + * @return See description. + * + * @throws ParseException If the durationStr doesn't conform to avax.xml.datatype.Duration. + */ + public static Date addDuration(String durationStr, Date fromDate) throws ParseException + { + try + { + Duration duration = StringUtils.notEmpty(durationStr) ? DatatypeFactory.newInstance().newDuration(durationStr) : null; + + return addDuration(duration, fromDate); + } + catch (DatatypeConfigurationException ex) + { + // This is not good there is nothing we can do. Throw a parse exception + throw new ParseException("Invalid durationStr given: '"+durationStr+"'. It must be a value as defined in javax.xml.datatype.Duration: "+ex.getMessage(), 0); + } + + } + + /** + * This method adds the duration to the given 'fromDate'. The fromDate is not altered instead the final result is returned. + * + * @param duration if null then the 'fromDate' is returned. + * @param fromDate Can be null. In this case the 'current date' is used. + * + * @return See description. + */ + public static Date addDuration(Duration duration, Date fromDate) + { + Date newDate = (fromDate != null) ? new Date(fromDate.getTime()) : new Date(); + + if (duration != null) + { + duration.addTo(newDate); + } + + return newDate; + } + public static String nowAsISO8601() { // TimeZone tz = TimeZone.getTimeZone("UTC"); diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/CommonConstants.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/CommonConstants.java index d77571a3..9e704d15 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/CommonConstants.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/CommonConstants.java @@ -72,6 +72,9 @@ public class CommonConstants /* SOAP transport String: Future Use */ public static final String SOAP_TRANSPORT_STR = "SOAP"; + + /* Used in 'blocking' ExecutorService to allow a max sleep time before shutdown is checked */ + public final static long MAX_SLEEP_MILLISEC = 5 * MILISEC; /*-----------------------------------------------------------*/ /* Constants defined by SIF3 Spec. */ @@ -118,7 +121,22 @@ public enum QueueStrategy {ADAPTER_LEVEL, ZONE_LEVEL, OBJECT_LEVEL}; */ public enum QueuePollingType {IMMEDIATE, LONG}; - /*------------------------------------------------------------------------------------*/ + /* + * Job states (for functional services) + */ + public enum JobState {NOTSTARTED, INPROGRESS, COMPLETED, FAILED} + + /* + * Phase states (for functional services) + */ + public enum PhaseState {NOTAPPLICABLE, NOTSTARTED, PENDING, SKIPPED, INPROGRESS, COMPLETED, FAILED} + + /* + * ACLs are applied in many places. This enum defines the plaves where they can apply. + */ + public enum RightType {SERVICE, PHASE, STATE}; + + /*------------------------------------------------------------------------------------*/ /* URL Query Parameter names in relation to security (special case for SIF Express) --*/ /*------------------------------------------------------------------------------------*/ public enum AuthenticationType {Basic, SIF_HMACSHA256, Other}; @@ -143,5 +161,6 @@ public enum AuthenticationType {Basic, SIF_HMACSHA256, Other}; /* HTTP Status that are not listed in the Response.Status class --*/ /*----------------------------------------------------------------*/ public static final int RESPONSE_TOO_LARGE = 413; + public static final int NOT_IMPLEMENTED = 501; } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/DataTooLargeException.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/DataTooLargeException.java index 3e4eedca..21ffbe59 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/DataTooLargeException.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/DataTooLargeException.java @@ -1,7 +1,7 @@ /* * DataTooLargeException.java Created: 23/04/2015 * - * Copyright 2015 Systemic Pty Ltd + * Copyright 2015-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,27 +16,86 @@ package sif3.common.exception; -public class DataTooLargeException extends Exception +import sif3.common.CommonConstants; +import sif3.common.ws.ErrorDetails; + +/** + * Exception that can be used by providers if a request is received that would result into response payload that is too large. + * Generally this is the case for unbound queries or where the 'page size' of a query is too large. It defaults the HTTP Status to 413 + * (Response too large) which indicates that the request cannot be served. + * + * @author Joerg Huber + * + */ +public class DataTooLargeException extends SIFException { private static final long serialVersionUID = 8201196298932433571L; - public DataTooLargeException() - { - super(); - } + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message) + { + this(message, null, null, null); + } + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message, Throwable cause) + { + this(message, null, null, cause); + } - public DataTooLargeException(String msg) - { - super(msg); - } + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message, String description) + { + this(message, description, null, null); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message, String description, Throwable cause) + { + this(message, description, null, cause); + } - public DataTooLargeException(String msg, Throwable ex) - { - super(msg, ex); - } + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message, String description, String scope) + { + this (message, description, scope, null); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public DataTooLargeException(String message, String description, String scope, Throwable cause) + { + super(new ErrorDetails(CommonConstants.RESPONSE_TOO_LARGE, message, description, scope), cause); + } - public DataTooLargeException(Throwable ex) - { - super(ex); - } +// public DataTooLargeException() +// { +// super(); +// } +// +// public DataTooLargeException(String msg) +// { +// super(msg); +// } +// +// public DataTooLargeException(String msg, Throwable ex) +// { +// super(msg, ex); +// } +// +// public DataTooLargeException(Throwable ex) +// { +// super(ex); +// } } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ExpiredException.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ExpiredException.java new file mode 100644 index 00000000..a52c0dd1 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ExpiredException.java @@ -0,0 +1,82 @@ +/* + * ExpiredException.java + * Created: 28 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.exception; + +import javax.ws.rs.core.Response.Status; + +import sif3.common.ws.ErrorDetails; + +/** + * Exception that can be used by providers if a resource, session or anything else is expired/gone. It defaults the HTTP Status to 410 + * which indicates that the resource/service/session etc has expired. + * + * @author Joerg Huber + * + */ +public class ExpiredException extends SIFException +{ + private static final long serialVersionUID = 1142998365326590226L; + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public ExpiredException(String message) + { + this(message, null, null, null); + } + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public ExpiredException(String message, Throwable cause) + { + this(message, null, null, cause); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public ExpiredException(String message, String description) + { + this(message, description, null, null); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public ExpiredException(String message, String description, Throwable cause) + { + this(message, description, null, cause); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public ExpiredException(String message, String description, String scope) + { + this (message, description, scope, null); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public ExpiredException(String message, String description, String scope, Throwable cause) + { + super(new ErrorDetails(Status.GONE.getStatusCode(), message, description, scope), cause); + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ResourceNotFoundException.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..02babdb4 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/ResourceNotFoundException.java @@ -0,0 +1,83 @@ +/* + * ResourceNotFoundException.java + * Created: 11 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.exception; + +import javax.ws.rs.core.Response.Status; + +import sif3.common.ws.ErrorDetails; + +/** + * Exception that can be used by providers if a resource is not found. It defaults the HTTP Status to 404 which indicates + * that the resource/service is not found. + * + * @author Joerg Huber + * + */ +public class ResourceNotFoundException extends SIFException +{ + private static final long serialVersionUID = 2642998385326590026L; + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message) + { + this(message, null, null, null); + } + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message, Throwable cause) + { + this(message, null, null, cause); + } + + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message, String description) + { + this(message, description, null, null); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message, String description, Throwable cause) + { + this(message, description, null, cause); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message, String description, String scope) + { + this (message, description, scope, null); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public ResourceNotFoundException(String message, String description, String scope, Throwable cause) + { + super(new ErrorDetails(Status.NOT_FOUND.getStatusCode(), message, description, scope), cause); + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/SIFException.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/SIFException.java new file mode 100644 index 00000000..58e45cc3 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/SIFException.java @@ -0,0 +1,119 @@ +/* + * SIFException.java + * Created: 11 Jan 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.exception; + +import sif3.common.ws.ErrorDetails; + +/** + * This exception is a generic SIF Exception that can be used to raise within the Framework. It is intended to be use by + * adapters where an exception needs to be translated in an SIF Error Message (XML or JSON). It encapsulates the information + * that can be used in a SIF Error Message. As HTTP Status is always required when throwing this exception to ensure that + * a proper SIF Error Message can be produced. + * + * NOT YET USED IN FRAMEWORK. FUTURE EXTENTION. + * + * @author Joerg Huber + * + */ +public class SIFException extends Exception +{ + private static final long serialVersionUID = -9199894564131208357L; + + private ErrorDetails error = null; + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message) + { + this(httpErrorStatus, message, null, null, null); + } + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message, Throwable cause) + { + this(httpErrorStatus, message, null, null, cause); + } + + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message, String description) + { + this(httpErrorStatus, message, description, null, null); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message, String description, Throwable cause) + { + this(httpErrorStatus, message, description, null, cause); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message, String description, String scope) + { + this (httpErrorStatus, message, description, scope, null); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public SIFException(int httpErrorStatus, String message, String description, String scope, Throwable cause) + { + this (new ErrorDetails(httpErrorStatus, message, description, scope), cause); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters. The + * values will be extracted form the error parameter. If any value is set to null then it is not passed to the SIF Error + * message. The error.code should be set otherwise it assumes HTTP Status 400. + */ + public SIFException(ErrorDetails error) + { + this(error, null); + } + + public SIFException(ErrorDetails error, Throwable cause) + { + super(error.getMessage(), cause); + if (error.getErrorCode() < 100) + { + error.setErrorCode(400); + } + + this.error = error; + } + + public ErrorDetails getErrorDetails() + { + return error; + } + + @Override + public String getMessage() + { + return getErrorDetails().getMessage(); + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/UnsupportedQueryException.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/UnsupportedQueryException.java index c0f40ff2..d86cc28c 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/UnsupportedQueryException.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/exception/UnsupportedQueryException.java @@ -2,7 +2,7 @@ * UnsupportedQueryException.java * Created: 23/09/2013 * - * Copyright 2013 Systemic Pty Ltd + * Copyright 2013-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,27 +18,91 @@ package sif3.common.exception; -public class UnsupportedQueryException extends Exception -{ - private static final long serialVersionUID = 907365345457L; +import javax.ws.rs.core.Response.Status; + +import sif3.common.ws.ErrorDetails; - public UnsupportedQueryException() - { - super(); - } +/** + * Exception that can be used by providers if a service doesn't support a particular query type (i.e. QBE). It defaults the HTTP Status to 400 + * (Bad Request) which indicates that the query is not supported. + * + * @author Joerg Huber + * + */ +public class UnsupportedQueryException extends SIFException +{ + private static final long serialVersionUID = -7852913016152922737L; - public UnsupportedQueryException(String msg) - { - super(msg); - } + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message) + { + this(message, null, null, null); + } + + /* + * The equivalent SIF Error will have the code & message set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message, Throwable cause) + { + this(message, null, null, cause); + } - public UnsupportedQueryException(String msg, Throwable ex) - { - super(msg, ex); - } + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message, String description) + { + this(message, description, null, null); + } + + /* + * The equivalent SIF Error will have the code, message & description set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message, String description, Throwable cause) + { + this(message, description, null, cause); + } - public UnsupportedQueryException(Throwable ex) - { - super(ex); - } + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message, String description, String scope) + { + this (message, description, scope, null); + } + + /* + * The equivalent SIF Error will have the code, message, description & scope set. Message is limited to 1024 Characters + */ + public UnsupportedQueryException(String message, String description, String scope, Throwable cause) + { + super(new ErrorDetails(Status.BAD_REQUEST.getStatusCode(), message, description, scope), cause); + } + + +//public class UnsupportedQueryException extends Exception +//{ +// private static final long serialVersionUID = 907365345457L; +// +// public UnsupportedQueryException() +// { +// super(); +// } +// +// public UnsupportedQueryException(String msg) +// { +// super(msg); +// } +// +// public UnsupportedQueryException(String msg, Throwable ex) +// { +// super(msg, ex); +// } +// +// public UnsupportedQueryException(Throwable ex) +// { +// super(ex); +// } } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/HeaderValues.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/HeaderValues.java index 7a0f56e5..d38595b8 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/HeaderValues.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/HeaderValues.java @@ -54,7 +54,7 @@ public enum MethodType {DELETE, UPDATE, GET, POST}; /** * Valid values for the "serviceType" header field in the request. */ - public enum ServiceType {OBJECT, FUNCTION, UTILITY, SERVICEPATH, XQUERYTEMPLATE}; + public enum ServiceType {OBJECT, FUNCTIONAL, UTILITY, SERVICEPATH, XQUERYTEMPLATE}; /** * Valid values for the "requestType" header field in the request. diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/RequestHeaderConstants.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/RequestHeaderConstants.java index 18c8822f..7c311bca 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/RequestHeaderConstants.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/RequestHeaderConstants.java @@ -36,7 +36,7 @@ public class RequestHeaderConstants public static final String HDR_AUTH_TOKEN = HttpHeaders.AUTHORIZATION; public static final String HDR_GENERATOR_ID = "generatorId"; public static final String HDR_MESSAGE_TYPE = "messageType"; // RESPONSE, EVENT, ERROR - public static final String HDR_SERVICE_TYPE = "serviceType"; // OBJECT, FUNCTION, UTILITY, SERVICEPATH, XQUERYTEMPLATE + public static final String HDR_SERVICE_TYPE = "serviceType"; // OBJECT, FUNCTIONAL, UTILITY, SERVICEPATH, XQUERYTEMPLATE public static final String HDR_DATE_TIME = "timestamp"; public static final String HDR_SOURCE_NAME = "sourceName"; public static final String HDR_FINGERPRINT = "fingerprint"; diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/ResponseHeaderConstants.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/ResponseHeaderConstants.java index d1173012..88a7c1c0 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/ResponseHeaderConstants.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/header/ResponseHeaderConstants.java @@ -45,7 +45,7 @@ public class ResponseHeaderConstants public static final String HDR_REQUEST_ID = "requestId"; public static final String HDR_RESPONSE_ACTION = "responseAction"; // CREATE, UPDATE, DELETE, QUERY public static final String HDR_REL_SERVICE_PATH = "relativeServicePath"; - public static final String HDR_SERVICE_TYPE = "serviceType"; // OBJECT, FUNCTION, UTILITY, SERVICEPATH, XQUERYTEMPLATE + public static final String HDR_SERVICE_TYPE = "serviceType"; // OBJECT, FUNCTIONAL, UTILITY, SERVICEPATH, XQUERYTEMPLATE /* Paging and navigation related properties */ public static final String HDR_NAVIGATION_ID = CommonConstants.PagingResponseProperty.navigationId.name(); //"navigationId"; diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/ChangesSinceProvider.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/ChangesSinceProvider.java index a4731d00..83dae44f 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/ChangesSinceProvider.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/ChangesSinceProvider.java @@ -2,7 +2,7 @@ * ChangesSinceProvider.java * Created: 06/01/2016 * - * Copyright 2016 Systemic Pty Ltd + * Copyright 2016-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,9 @@ package sif3.common.interfaces; import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.ExpiredException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnsupportedQueryException; import sif3.common.model.ChangedSinceInfo; import sif3.common.model.PagingInfo; @@ -90,12 +92,16 @@ public interface ChangesSinceProvider * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws ExpiredException If the passed in changedSinceInfo.changesSinceMarker is no longer valid or has expired. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ public Object getChangesSince(SIFZone zone, SIFContext context, PagingInfo pagingInfo, ChangedSinceInfo changedSinceInfo, RequestMetadata metadata, - ResponseParameters customResponseParams) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; + ResponseParameters customResponseParams) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, ExpiredException, SIFException; } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Consumer.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Consumer.java index 351e62de..b61b1448 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Consumer.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Consumer.java @@ -2,7 +2,7 @@ * Consumer.java * Created: 23/09/2013 * - * Copyright 2013 Systemic Pty Ltd + * Copyright 2013-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,8 @@ import sif3.common.exception.PersistenceException; import sif3.common.exception.ServiceInvokationException; -import sif3.common.exception.UnsupportedQueryException; -import sif3.common.header.HeaderValues.QueryIntention; import sif3.common.header.HeaderValues.RequestType; import sif3.common.model.CustomParameters; -import sif3.common.model.PagingInfo; import sif3.common.model.ZoneContextInfo; import sif3.common.ws.BulkOperationResponse; import sif3.common.ws.CreateOperationStatus; @@ -34,24 +31,25 @@ import sif3.common.ws.Response; /** - * This class defines the methods a consumer must implement to fit with this framework. The interface is independent from the - * Data Model and underlying infrastructure components. It defines the core function SIF3 specifies for a consumer.

+ * This class defines the methods a Object Service Consumer must implement to fit with this framework. The interface is independent from the + * Data Model and underlying infrastructure components. It defines the core function SIF3 specifies for a consumer. This interface defines + * additional methods to the MinimalConsumer. These methods are applicable for standard CRUD operations for an Object Service Consumers.

* * Note:
- * Because this framework allows to be run under Java 6 some of the types in various methods use "Object" instead of the template + * Because this framework used to be run under Java 6 some of the types in various methods use "Object" instead of the template * notation. This is because Java 6 doesn't allow a 'new T()' and therefore the interface avoids the template notation to not break * the implementation where a constructor for an Object might be required. This may change in future versions of the framework. * * @author Joerg Huber */ -public interface Consumer extends DataModelLink +public interface Consumer extends MinimalConsumer { /*------------------------------*/ /*-- Single Object Operations --*/ /*------------------------------*/ /** - * This method creates the given object for the list of environments, zones and contexts. + * This method creates the given object for the list of zones and contexts. * * @param data The object that shall be created. * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the @@ -200,99 +198,99 @@ public interface Consumer extends DataModelLink */ public List> deleteMany(List resourceIDs, List zoneCtxList, RequestType requestType, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; - /*----------------------*/ - /*-- Query Operations --*/ - /*----------------------*/ + /*------------------------------------------------------------*/ + /*-- Query Operations: Covered in MinimalConsumer interface --*/ + /*------------------------------------------------------------*/ - /** - * This method returns an object with the given resourceId. - * - * @param resourceID The Id of the object to be returned. - * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the - * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given - * environment has more than one zone and/or context and the operation shall be performed in any number of them - * then each combination must be a separate entry in this list. - * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. - * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed - * by this framework then the value in this parameter will be overridden with the internally - * managed HTTP header field. The same applies to the URL Query parameters. This parameter can - * be null. - * - * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the - * service in the default zone and context. - * - * @throws IllegalArgumentException One of the parameters is invalid. - * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. - * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. - */ - public List retrievByPrimaryKey(String resourceID, List zoneCtxList, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; - - /** - * This method is used to retrieve any number of objects. This is achieved in terms of 'paging' through the list of objects. The consumer - * is expected to provide paging information to tell the provider which objects in the list shall be returned. The first page has - * the number 0. - * - * @param pagingInfo Page information to be set for the provider to determine which results to return. - * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the - * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given - * environment has more than one zone and/or context and the operation shall be performed in any number of them - * then each combination must be a separate entry in this list. - * @param requestType Indicating if the retrieve request is synchronous (IMMEDIATE) or if it shall be shall executed asynchronously (DELAYED). - * @param queryIntention Indicating what the intention of the query and follow-up queries is. Can be set to null which - * will default to 'ONE-OFF' - * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. - * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed - * by this framework then the value in this parameter will be overridden with the internally - * managed HTTP header field. The same applies to the URL Query parameters. This parameter can - * be null. - * - * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the - * service in the default zone and context. - * - * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) - * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. - * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. - * - */ - public List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; +// /** +// * This method returns an object with the given resourceId. +// * +// * @param resourceID The Id of the object to be returned. +// * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the +// * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given +// * environment has more than one zone and/or context and the operation shall be performed in any number of them +// * then each combination must be a separate entry in this list. +// * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. +// * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed +// * by this framework then the value in this parameter will be overridden with the internally +// * managed HTTP header field. The same applies to the URL Query parameters. This parameter can +// * be null. +// * +// * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the +// * service in the default zone and context. +// * +// * @throws IllegalArgumentException One of the parameters is invalid. +// * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. +// * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. +// */ +// public List retrievByPrimaryKey(String resourceID, List zoneCtxList, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; +// +// /** +// * This method is used to retrieve any number of objects. This is achieved in terms of 'paging' through the list of objects. The consumer +// * is expected to provide paging information to tell the provider which objects in the list shall be returned. The first page has +// * the number 0. +// * +// * @param pagingInfo Page information to be set for the provider to determine which results to return. +// * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the +// * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given +// * environment has more than one zone and/or context and the operation shall be performed in any number of them +// * then each combination must be a separate entry in this list. +// * @param requestType Indicating if the retrieve request is synchronous (IMMEDIATE) or if it shall be shall executed asynchronously (DELAYED). +// * @param queryIntention Indicating what the intention of the query and follow-up queries is. Can be set to null which +// * will default to 'ONE-OFF' +// * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. +// * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed +// * by this framework then the value in this parameter will be overridden with the internally +// * managed HTTP header field. The same applies to the URL Query parameters. This parameter can +// * be null. +// * +// * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the +// * service in the default zone and context. +// * +// * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) +// * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. +// * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. +// * +// */ +// public List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; - /*-------------------------------*/ - /*-- Other required Operations --*/ - /*-------------------------------*/ - - /** - * Will invoke the REST HEAD call to retrieve some information about a service. It will not return a payload as per HTTP - * Specification of the HEAD method. Besides a status code and an optional status message only a set of HTTP Header properties - * will be set in the returned responses. These HTTP Header properties can be retrieved as part of the returned response - * object (response.getHdrProperties()).
- * Because this method almost mirrors the HTTP GET for the root object service all parameters that would make up the retrieve() - * method in this class are supported. The exception is the requestType and queryIntention parameter that are allowed in the - * retrieve() method. They do not make any sense for this method and are therefore omitted. - * - * @param pagingInfo Page information to be set for the provider to determine which results to return. - * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the - * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given - * environment has more than one zone and/or context and the operation shall be performed in any number of them - * then each combination must be a separate entry in this list. - * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. - * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed - * by this framework then the value in this parameter will be overridden with the internally - * managed HTTP header field. The same applies to the URL Query parameters. This parameter can - * be null. - * - * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the - * information from the service in the default zone and context. - * The main bit of useful information returned by this call are the HTTP Header properties that can be retrieved with the - * response.getHdrProperties() method. - * - * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) - * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. - * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. - * - */ - public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; - +// /*-------------------------------*/ +// /*-- Other required Operations --*/ +// /*-------------------------------*/ - /** Call this at shut down time. */ - public void finalise(); +// /** +// * Will invoke the REST HEAD call to retrieve some information about a service. It will not return a payload as per HTTP +// * Specification of the HEAD method. Besides a status code and an optional status message only a set of HTTP Header properties +// * will be set in the returned responses. These HTTP Header properties can be retrieved as part of the returned response +// * object (response.getHdrProperties()).
+// * Because this method almost mirrors the HTTP GET for the root object service all parameters that would make up the retrieve() +// * method in this class are supported. The exception is the requestType and queryIntention parameter that are allowed in the +// * retrieve() method. They do not make any sense for this method and are therefore omitted. +// * +// * @param pagingInfo Page information to be set for the provider to determine which results to return. +// * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the +// * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given +// * environment has more than one zone and/or context and the operation shall be performed in any number of them +// * then each combination must be a separate entry in this list. +// * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. +// * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed +// * by this framework then the value in this parameter will be overridden with the internally +// * managed HTTP header field. The same applies to the URL Query parameters. This parameter can +// * be null. +// * +// * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the +// * information from the service in the default zone and context. +// * The main bit of useful information returned by this call are the HTTP Header properties that can be retrieved with the +// * response.getHdrProperties() method. +// * +// * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) +// * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. +// * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. +// * +// */ +// public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; +// +// +// /** Call this at shut down time. */ +// public void finalise(); } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/EventProvider.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/EventProvider.java index c3ded1e0..ff80112d 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/EventProvider.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/EventProvider.java @@ -74,5 +74,7 @@ public interface EventProvider extends DataModelLink * @param context The context for which the events applied to. This parameter value can be null which indicated the default context. */ public void onEventError(SIFEvent sifEvent, SIFZone zone, SIFContext context); + + public void broadcastEvents(); } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceConsumer.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceConsumer.java new file mode 100644 index 00000000..74e22c85 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceConsumer.java @@ -0,0 +1,418 @@ +/* + * FunctionalServiceConsumer.java + * Created: 12 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.interfaces; + +import java.util.List; + +import javax.ws.rs.core.MediaType; + +import sif3.common.CommonConstants.PhaseState; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.ServiceInvokationException; +import sif3.common.header.HeaderValues.QueryIntention; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.model.CustomParameters; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.ZoneContextInfo; +import sif3.common.model.job.JobCreateRequestParameter; +import sif3.common.model.job.PhaseInfo; +import sif3.common.ws.BulkOperationResponse; +import sif3.common.ws.CreateOperationStatus; +import sif3.common.ws.OperationStatus; +import sif3.common.ws.Response; +import sif3.common.ws.job.PhaseDataRequest; + +/** + * This interface defines the core CRUD operations required by a functional service provider. Functional Services deal with Job Objects + * as well as phase objects. While operations and data model are defined in the SIF Infrastructure specification and can be specific in + * this interface in regards to types, phase operation objects are entirely implementation specific. Hence many phase operations only + * deal with "raw" data, that being strings and the appropriate mime type. It is up to the final implementation class to marshal or + * unmarshal the phase data objects. + * + * @author Joerg Huber + */ +public interface FunctionalServiceConsumer extends MinimalConsumer +{ + /*---------------------------*/ + /*-- Single Job Operations --*/ + /*---------------------------*/ + + /** + * This method creates the job for the list of zones and contexts. + * + * @param createJobRequest Data (parameters) for the job to be craeted. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. The object was created in the default zone and context. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + */ + public List createJob(JobCreateRequestParameter createJobRequest, List zoneCtxList, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; + + /** + * Removed the job with the given jobId. + * + * @param jobID The Id of the job to be removed. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. The object was deleted in the default zone and context. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + */ + public List deleteJob(String jobID, List zoneCtxList, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; + + /*-------------------------*/ + /*-- Bulk Job Operations --*/ + /*-------------------------*/ + + /** + * This method will create many jobs in one call. The 'createMultipleJobsRequest' is a list of job parameters. For each entry in that list + * a job create request will be issued. It is assumed that all jobs are of the same type. This method must create job refIDs and expects + * the provider to accept them. MustUseAdvisory must be set to true. + * + * @param createMultipleJobsRequest The list of job parameter data. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param requestType Indicating if the createJobs request is synchronous (IMMEDIATE) or if it shall be shall executed asynchronously (DELAYED). + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. The creation of data was in performed in the default zone and context. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + */ + public List> createJobs(List createMultipleJobsRequest, List zoneCtxList, RequestType requestType, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; + + /** + * This method removes all jobs in the jobIDs list in one hit. + * + * @param jobIDs A list of jobID for the jobs to be removed. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param requestType Indicating if the deleteJobs request is synchronous (IMMEDIATE) or if it shall be shall executed asynchronously (DELAYED). + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. The deletion of data was in performed in the default zone and context. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + */ + public List> deleteJobs(List jobIDs, List zoneCtxList, RequestType requestType, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; + + /*----------------------*/ + /*-- Phase Operations --*/ + /*----------------------*/ + /** + * This method is used to retrieve data from a given phase. This can be any data. It is up to the implementation of the + * functional service to know what that data is for a given phase. This framework is agnostic to that data.

+ * + * In case of an immediate request the response.dataObject property will hold the raw payload as a string and the + * response.dataObjectType will be String.class. The string value must represent the "marshalled" version of the + * data in the format indicated by the "returnMimeType".

+ * + * In case of a delayed request the these two properties are null and the response.delayedReceipt property will be + * populated accordingly.

+ * + * Because the data that can be returned as part of a phase might be a collection, the paging parameter can be provided. + * If the data to be returned is considered too large by the provider (implementation dependent) then an appropriate error is + * returned (HTTP Status 413 - Response too large). + * + * @param phaseInfo Hold the jobID and phase name of the job from where the data shall be retrieved. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED! Might be rejected + * by provider.). + * @param queryIntention Indicating what the intention of the query and follow-up queries is. Can be set to null which + * will default to 'ONE-OFF' + * @param zone The Zone for which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return Response Object holding appropriate values and results of the call. Because the framework is agnostic to the + * data that is returned for a phase the Response.dataObjectType will be set to "String" and the Response.dataObject + * will hold the string representation of the returned payload. It is up to the caller of this method to potentially + * marshal that payload into an appropriate object. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response retrieveDataFromPhase(PhaseInfo phaseInfo, + MediaType returnMimeType, + PagingInfo pagingInfo, + QueryIntention queryIntention, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException; + + /** + * This method should perform a "create" operation for the data given in the "phaseData.data" (string). This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). This is indicated with a HTTP status of 204 (No content). However if data + * is returned then response.dataObject will hold the data in its string representation and the response.dataObjectType will be + * set to String.class. It is expected that the consumer uses the response.mediaType to unmarshal the data to a structure suitable + * for the consumer. It is also expected that the response.mediaType would be of the same mime type as indicated by the consumer + * in the returnMimeType parameter.

+ * + * In case of a delayed request the the response.dataObject and response.dataObjectType are null and the response.delayedReceipt + * property will be populated accordingly.

+ * + * In case of a failure an appropriate error message is returned in the Response Object. If there is no error returned it is + * assumed that this method completed successfully and the appropriate HTTP status is set. + * + * @param phaseInfo Hold the jobID and phase name of the job where the data shall be created. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param useAdvisory If new IDs for the created data shall be allocated or used as given. In some cases that may not be applicable + * but if it is then this parameter indicates the expected behaviour. + * @param zone The Zone for which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. If the status is set to 204 then no content is returned which is also a valid scenario. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response createDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException; + + /** + * This method should perform an "update" operation for the data given in the "phaseData.data" (string). This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). However if data is returned then response.dataObject will hold the data in + * its string representation and the response.dataObjectType will be set to String.class. It is expected that the consumer uses + * the response.mediaType to unmarshal the data to a structure suitable for the consumer. It is also expected that the + * response.mediaType would be of the same mime type as indicated by the consumer in the returnMimeType parameter.

+ * + * In case of a delayed request the the response.dataObject and response.dataObjectType are null and the response.delayedReceipt + * property will be populated accordingly.

+ * + * In case of a failure an appropriate error message is returned in the Response Object. If there is no error returned it is + * assumed that this method completed successfully and the appropriate HTTP status is set. + * + * @param phaseInfo Hold the jobID and phase name of the job where the data shall be updated. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param zone The Zone for which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response updateDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException; + + /** + * This method should perform a "delete" operation for the data given in the "phaseData.data" (string). This can be any data. It + * is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The phaseData.mimeType shall be used + * by the provider to unmarshal the data into a useful data structure. It is important to note that there might be no return data + * for a phase operation (response.dataObject == null). However if data is returned then response.dataObject will hold the data in + * its string representation and the response.dataObjectType will be set to String.class. It is expected that the consumer uses + * the response.mediaType to unmarshal the data to a structure suitable for the consumer. It is also expected that the + * response.mediaType would be of the same mime type as indicated by the consumer in the returnMimeType parameter.

+ * + * In case of a delayed request the the response.dataObject and response.dataObjectType are null and the response.delayedReceipt + * property will be populated accordingly.

+ * + * In case of a failure an appropriate error message is returned in the Response Object. If there is no error returned it is + * assumed that this method completed successfully and the appropriate HTTP status is set. + * + * @param phaseInfo Hold the jobID and phase name of the job where the data shall be deleted. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param phaseDataRequest The data and its mime type that is given to the operation of this phase. The data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * appropriate error is returned to this consumer (HTTP Status 400 - Bad Request). + * @param zone The Zone for which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the the request is being issued. Can be Null (default Zone) + * @param requestType Indicating if IMMEDIATE or DELAYED request is desired. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return Response Object holding appropriate values of the call. The response may contain data (response.dataObject) that will be + * with a response.dataObjectType=String.class. The consumer is expected to use the response.mediaType to unmarshal the data + * into its internal structure. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response deleteDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseDataRequest, + MediaType returnMimeType, + SIFZone zone, + SIFContext context, + RequestType requestType, + CustomParameters customParameters) + throws IllegalArgumentException, ServiceInvokationException; + + /*----------------------------*/ + /*-- Phase State Operations --*/ + /*----------------------------*/ + /** + * This method attempts to update the state of a given phase for a job.
+ * An error message will be part of the returned Response for the following cases:
+ * - Job doesn't exist.
+ * - If the phase or state is invalid.
+ * - Consumer doesn't have appropriate permissions for this operation.
+ * - Potentially the job is no longer 'update-able' because it has reached an end state (eg. COMPLETED or FAILED).
+ * + * @param phaseInfo Hold the jobID and phase name of the job where the state shall be updated. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param newState The value of the new state for the given phase. If null then an IllegalArgumentException will be thrown. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. In this case it will be a StateType as defined + * in the infrastructure data model. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response updatePhaseState(PhaseInfo phaseInfo, PhaseState newState, SIFZone zone, SIFContext context, CustomParameters customParameters) throws IllegalArgumentException, ServiceInvokationException; + + /** + * This method attempts to retrieve the state(s) of the given phase. The returned object in the response will be of + * type StateCollectionType as defined in the infrastructure data model.
+ * An error message will be part of the returned Response for the following cases:
+ * - Job doesn't exist.
+ * - If the phase is invalid.
+ * - Consumer doesn't have appropriate permissions for this operation.
+ * + * @param phaseInfo Hold the jobID and phase name of the job where the states shall be retrieved. If the parameter or + * any of its properties is null/empty then an IllegalArgumentException will be thrown. + * @param zone The zone for which this operation shall be invoked. Can be null which indicates the DEFAULT zone. + * @param context The context for which this operation shall be invoked. Can be null which indicates the DEFAULT context. + * + * @return Response Object holding appropriate values and results of the call. In this case it will be a StateCollectionType as defined + * in the infrastructure data model. + * + * @throws ServiceInvokationException Any underlying errors occurred such as failure to invoke actual web-service etc. + * @throws IllegalArgumentException One of the parameters is invalid. See description of parameters. + */ + public Response getPhaseStates(PhaseInfo phaseInfo, SIFZone zone, SIFContext context, CustomParameters customParameters) throws IllegalArgumentException, ServiceInvokationException; + +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceProvider.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceProvider.java new file mode 100644 index 00000000..e73a6a3a --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/FunctionalServiceProvider.java @@ -0,0 +1,468 @@ +/* + * FunctionalServiceProvider.java + * Created: 07/06/2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.common.interfaces; + +import javax.ws.rs.core.MediaType; + +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.exception.DataTooLargeException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.exception.UnsupportedQueryException; +import sif3.common.header.HeaderProperties; +import sif3.common.model.PagingInfo; +import sif3.common.model.RequestMetadata; +import sif3.common.model.ResponseParameters; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.model.job.PhaseInfo; +import sif3.common.ws.job.PhaseDataRequest; +import sif3.common.ws.job.PhaseDataResponse; + +/** + * This class defines the methods a functional service provider must implement to fit with this framework. It defines the core function + * SIF3 specifies for a functional service provider.
+ * Note that each method has a number of parameters but they all have the following three given parameters. The zone, + * the context and metadata. The first two relate to the standard SIF concept of zone and context. The metadata parameter + * however is additional info that may be provided by a consumer with each request. This can be typical HTTP header + * fields such as generatorId, queryIntention etc. Please refer to the SIF 3 Infrastructure Service documentation what + * these fields mean as well as where they might be used. It is important to note that most of the properties in the + * metadata could be null and therefore implementations must take care how they are used.

+ * + * Note:
+ * Because this framework used to run under Java 6 some of the types in various methods use "Object" instead of the template + * notation. This is because Java 6 doesn't allow a 'new T()' and therefore the interface avoids the template notation to not break + * the implementation where a constructor for an Object might be required. This may change in future versions of the framework. + * Further it requires some objects form the Infrastructure data model but the sif3-infra-model module is not available in this + * common module the type 'Object' is used in a couple of places. Please refer to the documentation of the appropriate method + * for more details on what exact type shall be expected in these methods. + * + * @author Joerg Huber + */ +public interface FunctionalServiceProvider extends DataModelLink +{ + /*---------------------------*/ + /*-- Single Job Operations --*/ + /*---------------------------*/ + + /** + * This method creates the given job with the data provided in the given zone and context. If the job cannot be created then either + * an exception is raised or null is returned. + * + * @param jobData The data of the actual job to be created. It may or may not hold the jobID and the provider may or may not accept it. + * It is up to the implementation to make that decision. The final jobID is returned as part of the returned job. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the job shall be created. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * + * @return The job that is created. It may hold additional data than the one provided. This will be of type JobType as defined + * infrastructure data model. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public Object createJob(Object jobData, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; + + /** + * Removed the job with the given jobID in the given zone for the given context. + * + * @param jobID The Id of the job to be removed. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the job shall be removed. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the job. When it is passed to this method it not null but empty. + * + * @return TRUE: Job is removed. FALSE: Job does not exist. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + * + */ + public boolean deleteJob(String jobID, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; + + /*----------------------------*/ + /*-- Bulk Object Operations --*/ + /*----------------------------*/ + + /*-- None Required. All managed under the hood of the framework. --*/ + + /*----------------------------------------------*/ + /*-- Phase Operation invoked by the consumer. --*/ + /*----------------------------------------------*/ + + /** + * This method is used to retrieve data from a given phase. This can be any data. It is up to the implementation of the + * functional service to know what that data is for a given phase. This framework is agnostic to that data. The returned + * value is a String that must represent the "marshalled" version of the data in the format indicated by the "returnMimeType". + * Because the data that can be returned as part of a phase might be a collection, the paging parameter is provided. If the + * data to be returned is considered too large by the provider (implementation dependent) then a DataTooLargeException + * must be raised. This exception is then translated into an appropriate HTTP Status within the framework to meet the + * SIF Specification. + * + * @param phaseInfo The JobID and Phase Name for which the phase operation applies. Neither property is empty or null. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the data shall be returned. Can be Null (default Zone) + * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED!). + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * @param returnMimeType The mime type the response data is in. It is expected that the consumer provides that and the provider + * should attempt to marshal the data to the given mime type and return the resulting string as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * UnsupportedMediaTypeExcpetion must be raised. + * + * @return The response data (result set) with its mime type. It can be null indicating no or no further data available. + * The returned string should be the marshalled value of the result set and it should be in the mime type as + * indicated in the returnMimeType parameter. The status of the return value will be set by the framework and will be + * either HTTP 200 (OK) when data is returned or HTTP 204 (No Content) if there is no further data available. + * + * @throws UnsupportedMediaTypeExcpetion The provider cannot support the requested return media type. + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public PhaseDataResponse retrieveDataFromPhase(PhaseInfo phaseInfo, + SIFZone zone, + SIFContext context, + PagingInfo pagingInfo, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, DataTooLargeException, SIFException; + + /** + * This method should perform a "create" operation for the data given in the "PhaseDataRequest" parameter. This can be any data. + * It is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The mimeType property of the PhaseDataRequest + * parameter shall be used by the provider to unmarshal the data into a useful data structure. The response to this operation + * may or may not return any data. If no data is returned (PhaseDataResponse == null) then the consumer will retrieve a HTTP + * Status of the appropriate operation, in this case a HTTP Status of 201 (Created). If any other status shall be returned, + * even if there is no data available it is up to the implementer to set the appropriate Status in the PhaseDataResponse.status + * property.
+ * In case of any failures one of the listed exception must be raised which will translate into a standard SIF Error Message + * to be returned to the consumer. If no exception is raised the framework assumes that all is ok and the data of the + * PhaseDataResponse is returned. If data is available it is expected to be marshalled into the Mime Type requested in the + * returnMimeType parameter. The mime type of the response data shall also be set in the PhaseDataResponse. If not set it will + * attempt to use the mime type of returnMimeType. If that is not available it will assume the same mime type as in the request.

+ * The PhaseDataRequest.data can be null if no data is passed to this method. This is a valid scenario according to the + * SIF Specification. + * + * @param phaseInfo The JobID and Phase Name for which the phase operation applies. Neither property is empty or null. + * @param phaseData The data and its mime type that is given to the operation of this phase. That the data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param useAdvisory If new IDs for the created data shall be allocated or used as given. In some cases that may not be applicable + * but if it is then this parameter indicates the expected behaviour. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the objects shall be created. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * @param returnMimeType The mime type the PhaseDataResponse.data is in. It is expected that the consumer provides that and + * the provider if it expects data to be returned as part of this operation. The provider should attempt + * to marshal the PhaseDataResponse.data to the given mime type and return the resulting data as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * UnsupportedMediaTypeExcpetion must be raised. + * + * @return The response data of this operation with its mime type. It can be null if no data shall be returned. In this case + * the HTTP Status of 201 (Created) will be returned to the consumer. The returned PhaseDataResponse.data string if not + * null, should be the marshalled into the mime type as indicated in the returnMimeType parameter. See also description of + * this method about the handling of the PhaseDataResponse.mimeType property. + * + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws UnsupportedMediaTypeExcpetion The provider cannot support the requested media type. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public PhaseDataResponse createDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException; + + /** + * This method should perform an "update" operation for the data given in the "PhaseDataRequest" parameter. This can be any data. + * It is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The mimeType property of the PhaseDataRequest + * parameter shall be used by the provider to unmarshal the data into a useful data structure. The response to this operation + * may or may not return any data. If no data is returned (PhaseDataResponse == null) then the consumer will retrieve a HTTP + * Status of the appropriate operation, in this case a HTTP Status of 200 (Ok). If any other status shall be returned, + * even if there is no data available it is up to the implementer to set the appropriate Status in the PhaseDataResponse.status + * property.
+ * In case of any failures one of the listed exception must be raised which will translate into a standard SIF Error Message + * to be returned to the consumer. If no exception is raised the framework assumes that all is ok and the data of the + * PhaseDataResponse is returned. If data is available it is expected to be marshalled into the Mime Type requested in the + * returnMimeType parameter. The mime type of the response data shall also be set in the PhaseDataResponse. If not set it will + * attempt to use the mime type of returnMimeType. If that is not available it will assume the same mime type as in the request.

+ * The PhaseDataRequest.data can be null if no data is passed to this method. This is a valid scenario according to the + * SIF Specification. + * + * @param phaseInfo The JobID and Phase Name for which the phase operation applies. Neither property is empty or null. + * @param phaseData The data and its mime type that is given to the operation of this phase. That the data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the data shall be updated. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * @param returnMimeType The mime type the PhaseDataResponse.data is in. It is expected that the consumer provides that and + * the provider if it expects data to be returned as part of this operation. The provider should attempt + * to marshal the PhaseDataResponse.data to the given mime type and return the resulting data as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * UnsupportedMediaTypeExcpetion must be raised. + * + * @return The response data of this operation with its mime type. It can be null if no data shall be returned. In this case + * the HTTP Status of 200 (Ok) will be returned to the consumer. The returned PhaseDataResponse.data string if not + * null, should be the marshalled into the mime type as indicated in the returnMimeType parameter. See also description of + * this method about the handling of the PhaseDataResponse.mimeType property. + * + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws UnsupportedMediaTypeExcpetion The provider cannot support the requested media type. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public PhaseDataResponse updateDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException; + + /** + * This method should perform a "delete" operation for the data given in the "PhaseDataRequest" parameter. This can be any data. + * It is up to the implementation of the functional service to know what that data is for a given phase. This framework is agnostic + * to that data. The data is given in its raw form as received by the framework. The mimeType property of the PhaseDataRequest + * parameter shall be used by the provider to unmarshal the data into a useful data structure. The response to this operation + * may or may not return any data. If no data is returned (PhaseDataResponse == null) then the consumer will retrieve a HTTP + * Status of the appropriate operation, in this case a HTTP Status of 204 (No Content). If any other status shall be returned, + * even if there is no data available it is up to the implementer to set the appropriate Status in the PhaseDataResponse.status + * property.
+ * In case of any failures one of the listed exception must be raised which will translate into a standard SIF Error Message + * to be returned to the consumer. If no exception is raised the framework assumes that all is ok and the data of the + * PhaseDataResponse is returned. If data is available it is expected to be marshalled into the Mime Type requested in the + * returnMimeType parameter. The mime type of the response data shall also be set in the PhaseDataResponse. If not set it will + * attempt to use the mime type of returnMimeType. If that is not available it will assume the same mime type as in the request.

+ * The PhaseDataRequest.data can be null if no data is passed to this method. This is a valid scenario according to the + * SIF Specification. + * + * @param phaseInfo The JobID and Phase Name for which the phase operation applies. Neither property is empty or null. + * @param phaseData The data and its mime type that is given to the operation of this phase. That the data property of this + * parameter can be null in cases where no data is provided to this phase. This is entirely valid according + * to the SIF Specification. If data is provided (as a String) then the mime type is set as well, by the consumer, + * to indicate the format of the data. The provider can us this mime type to unmarshal the data into the + * appropriate internal structure. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the data shall be removed. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * @param returnMimeType The mime type the PhaseDataResponse.data is in. It is expected that the consumer provides that and + * the provider if it expects data to be returned as part of this operation. The provider should attempt + * to marshal the PhaseDataResponse.data to the given mime type and return the resulting data as + * part of this call. If the provider cannot marshal the data to the requested mime type then an + * UnsupportedMediaTypeExcpetion must be raised. + * + * @return The response data of this operation with its mime type. It can be null if no data shall be returned. In this case + * the HTTP Status of 204 (No Content) will be returned to the consumer. The returned PhaseDataResponse.data string if not + * null, should be the marshalled into the mime type as indicated in the returnMimeType parameter. See also description of + * this method about the handling of the PhaseDataResponse.mimeType property. + * + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws UnsupportedMediaTypeExcpetion The provider cannot support the requested media type. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public PhaseDataResponse deleteDataInPhase(PhaseInfo phaseInfo, + PhaseDataRequest phaseData, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams, + MediaType returnMimeType) + throws PersistenceException, UnsupportedMediaTypeExcpetion, SIFException; + + /*----------------------------------------------------------------------------------*/ + /*-- Phase Status Operation invoked by consumer and may need actions by provider. --*/ + /*----------------------------------------------------------------------------------*/ + + /** + * This method is called on the provider when a consumer calls a state change for a phase. It is assumed that underlying + * housekeeping functionality related to the framework is already done. This call is simply to inform the provider that the + * change has happen. It allows the provider to perform some additional work that may relate to such a phase state change. + * Such a phase state change may mean that the provider has to do some work or move to the next phase. It is up to the + * provider of this functional service to determine what need to be done. Calls to methods listed in the + * "Operations required by Provider to manage job and phase states" section in this class may be invoked as part of this + * method implementation. + * + * @param phaseInfo The job ID and phase name for which a phase change was requested. + * @param newState The new state of the phase. + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the request applies. Can be Null (default Zone) + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * @param customResponseParams Values to be returned as part of the response. These are generally just HTTP Header fields. If a developer + * sets the HTTP Header of a well defined SIF3 HTTP Header (i.e. providerId, timestamp) then the framework + * may override these with its own value to ensure the correct use and workings of the framework. It is the + * developer who will populate the object. When it is passed to this method it not null but empty. + * + * @throws PersistenceException + * @throws SIFException + */ + public void phaseStateUpdatedByConsumer(PhaseInfo phaseInfo, + PhaseState newState, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws PersistenceException, SIFException; + + /*-----------------------------------------*/ + /*-- Special Operations (i.e. HTTP HEAD) --*/ + /*-----------------------------------------*/ + + /** + * This method retrieves information about the root level service (i.e. .../StudentPersonals). The information that is returned is + * put into HTTP Headers in the response. This method is representing the HTTP HEAD functionality. It pretends to be the same + * as the retrieve() method without any payload to be returned, though. It is up to the implementation if an actual paged query shall + * be performed as part of this call. It can but doesn't have to. All standard checks as with the corresponding retrieve() method + * are being performed (i.e. check ACLs, sessionToken etc.). + * + * @param zone The Zone from which the request is being issued. Can be Null (default Zone) + * @param context The Context for which the information shall be returned. Can be Null (default Zone) + * @param pagingInfo Page information to determine which results to return. Null = Return all (NOT RECOMMENDED!). + * @param metadata Metadata relating to the request. Note that most of the properties might be null. + * + * @return Header Properties that shall be returned as HTTP Headers to the caller. Note this can be null or any number of + * custom HTTP headers. Generally one would expect that to be null. + * + * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) + * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the + * message of the exceptions holds some info. + * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. + */ + public HeaderProperties getServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; + + /*---------------------------------------------------------------------*/ + /*-- Operations required by Provider to manage job and phase states. --*/ + /*---------------------------------------------------------------------*/ + + /** + * This method updates the overall Job state for the given job. It is intended to be used by the provider only. If events + * are turned on it will create the appropriate event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param newState The new state of the job. If null then no action is taken. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updateJobState(String jobID, JobState newState) + throws PersistenceException; + + + /** + * This method updates the state for the given phase of the given job. If the job or phase doesn't exist then no action + * is taken. If events are turned on it will create the appropriate event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param phaseName The phase for which the state shall be updated. If phase doesn't exist then no action is taken. + * @param newState The new state of the job. If null then no action is taken. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updatePhaseState(String jobID, String phaseName, PhaseState newState) + throws PersistenceException; + + /** + * This method updates the overall Job status as well as the status of the given phase. This is a convenience method, so that + * a provider can update a phase state which in turn may also need an update to the overall job state. Using this method + * is more efficient then using two separate methods to update job and a phase state separately. It also reduces the number + * of events that will be created in case events are turned on. This method will only create on final event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param newJobState The new state of the job. If null then the state will not be updated. + * @param phaseName The phase for which the state shall be updated. If phase doesn't exist then no action is taken. + * @param newPhaseState The new state of the given phase. If null then the state will not be updated. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updateJobStateAndPhaseState(String jobID, JobState newJobState, String phaseName, PhaseState newPhaseState) + throws PersistenceException; + + + /*-------------------------------*/ + /*-- Other required Operations --*/ + /*-------------------------------*/ + /** + * This method is called when a provider shuts down. Can be used to clean-up internally held resources etc. + */ + public void finalise(); + + /** + * This method checks if the given state represents a Job End State as listed in the provider properties file in + * the property called 'job.endStates'. If it is then TRUE is returned otherwise FALSE is returned. + * + * @param state The state to test against the list of possible end states. + * + * @return TRUE: It is an end state. FALSE it is not an end state. + */ + public boolean isJobEndState(JobState state); +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/MinimalConsumer.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/MinimalConsumer.java new file mode 100644 index 00000000..401a0a82 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/MinimalConsumer.java @@ -0,0 +1,138 @@ +/* + * MinimalConsumer.java + * Created: 12 Jul 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.interfaces; + +import java.util.List; + +import sif3.common.exception.PersistenceException; +import sif3.common.exception.ServiceInvokationException; +import sif3.common.header.HeaderValues.QueryIntention; +import sif3.common.header.HeaderValues.RequestType; +import sif3.common.model.CustomParameters; +import sif3.common.model.PagingInfo; +import sif3.common.model.ZoneContextInfo; +import sif3.common.ws.Response; + +/** + * This is a cut down version of a basic consumer interface. It simply defines the data model link and some basic housekeeping methods that + * are required by all consumer interfaces. It is not intended that there is a stand alone implementation of this interface, rather that + * additional consumer interfaces extend this one. Classes that finally implement appropriate interfaces can use generic implementation for + * methods defined in this mini interface while consumer specific functions for Object or Functional Services can add additional interface + * methods. + * The main methods covered in this mini interface are data retrieval. + * + * @author Joerg Huber + */ +public interface MinimalConsumer extends DataModelLink +{ + /*----------------------*/ + /*-- Query Operations --*/ + /*----------------------*/ + + /** + * This method returns an object with the given resourceId. + * + * @param resourceID The Id of the object to be returned. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. + * + * @throws IllegalArgumentException One of the parameters is invalid. + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + */ + public List retrievByPrimaryKey(String resourceID, List zoneCtxList, CustomParameters customParameters) throws IllegalArgumentException, PersistenceException, ServiceInvokationException; + + /** + * This method is used to retrieve any number of objects. This is achieved in terms of 'paging' through the list of objects. The consumer + * is expected to provide paging information to tell the provider which objects in the list shall be returned. The first page has + * the number 0. + * + * @param pagingInfo Page information to be set for the provider to determine which results to return. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param requestType Indicating if the retrieve request is synchronous (IMMEDIATE) or if it shall be shall executed asynchronously (DELAYED). + * @param queryIntention Indicating what the intention of the query and follow-up queries is. Can be set to null which + * will default to 'ONE-OFF' + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * service in the default zone and context. + * + * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + * + */ + public List retrieve(PagingInfo pagingInfo, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, CustomParameters customParameters) throws PersistenceException, ServiceInvokationException; + + + /*-------------------------------*/ + /*-- Other required Operations --*/ + /*-------------------------------*/ + + /** + * Will invoke the REST HEAD call to retrieve some information about a service. It will not return a payload as per HTTP + * Specification of the HEAD method. Besides a status code and an optional status message only a set of HTTP Header properties + * will be set in the returned responses. These HTTP Header properties can be retrieved as part of the returned response + * object (response.getHdrProperties()).
+ * Because this method almost mirrors the HTTP GET for the root service all parameters that would make up such a call are + * supported. The exception is the requestType and queryIntention parameter that are allowed in the standard GET methods. + * They do not make any sense for this method and are therefore omitted. + * + * @param pagingInfo Page information to be set for the provider to determine which results to return. + * @param zoneCtxList If this List is null or empty then it is assumed that the operation is performed for the default Zone and Context for the + * connected. If this list is not empty then the action is performed for all Zone and Context listed. If a given + * environment has more than one zone and/or context and the operation shall be performed in any number of them + * then each combination must be a separate entry in this list. + * @param customParameters Custom HTTP Header fields and Custom URL Query parameters that will be added to the request. + * If any of the HTTP header fields correspond to a SIF pre-defined HTTP header that is managed + * by this framework then the value in this parameter will be overridden with the internally + * managed HTTP header field. The same applies to the URL Query parameters. This parameter can + * be null. + * + * @return A list of responses corresponding to the List of envZoneCtx. If the envZoneCtx was null then only one response will be returned for the + * information from the service in the default zone and context. + * The main bit of useful information returned by this call are the HTTP Header properties that can be retrieved with the + * response.getHdrProperties() method. + * + * @throws PersistenceException Some data could not be retrieved. An error log entry is performed and the message of the exceptions holds some info. + * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. + * + */ + public List getServiceInfo(PagingInfo pagingInfo, List zoneCtxList, CustomParameters customParameters) throws PersistenceException, ServiceInvokationException; + + + /** Call this at shut down time. */ + public void finalise(); + +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Provider.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Provider.java index cf864f7a..a5321892 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Provider.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/Provider.java @@ -2,7 +2,7 @@ * Provider.java * Created: 29/09/2013 * - * Copyright 2013 Systemic Pty Ltd + * Copyright 2013-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnsupportedQueryException; import sif3.common.header.HeaderProperties; import sif3.common.model.PagingInfo; @@ -73,8 +74,15 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public Object retrievByPrimaryKey(String resourceID, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public Object retrievByPrimaryKey(String resourceID, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /** * This method creates the given object with the data provided in the given zone and context. If the object cannot be created then either @@ -96,8 +104,16 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public Object createSingle(Object data, boolean useAdvisory, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public Object createSingle(Object data, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /** * This method updates the object given by its resourceID in the given zone for the given context. @@ -118,8 +134,16 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public boolean updateSingle(Object data, String resourceID, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public boolean updateSingle(Object data, + String resourceID, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /** * Removed the object with the given resourceId in the given zone for the given context. @@ -138,8 +162,15 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public boolean deleteSingle(String resourceID, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public boolean deleteSingle(String resourceID, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /*----------------------------*/ /*-- Bulk Object Operations --*/ @@ -148,7 +179,7 @@ public interface Provider extends DataModelLink /** * This method is used to retrieve any number of objects. This is achieved in terms of 'paging' through the list of objects. The consumer * is expected to provide paging information to tell the provider which objects in the list shall be returned. The first page has - * the number 0. The number of records to be returned are determined by the information within the + * the number 1. The number of records to be returned are determined by the information within the * paging info parameter. If the data set to be returned is considered too large by the provider (implementation * dependent) then a DataTooLargeException must be raised. This exception is then translated into an appropriate * HTTP Status within the framework to meet the SIF Specification. @@ -166,8 +197,15 @@ public interface Provider extends DataModelLink * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public Object retrieve(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata, ResponseParameters customResponseParams) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; + public Object retrieve(SIFZone zone, + SIFContext context, + PagingInfo pagingInfo, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; /** * This method will create many objects in one call. The 'data' parameter is a collection-style object that is defined in the data @@ -188,8 +226,16 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public List createMany(Object data, boolean useAdvisory, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public List createMany(Object data, + boolean useAdvisory, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /** * This method will update many objects in one call. The 'data' parameter is a collection-style object that is defined in the data @@ -210,8 +256,15 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public List updateMany(Object data, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public List updateMany(Object data, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /** * This method removes all objects in the resourceIDs list in one hit. @@ -230,8 +283,15 @@ public interface Provider extends DataModelLink * @throws IllegalArgumentException One of the parameters is invalid. * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public List deleteMany(List resourceIDs, SIFZone zone, SIFContext context, RequestMetadata metadata, ResponseParameters customResponseParams) throws IllegalArgumentException, PersistenceException; + public List deleteMany(List resourceIDs, + SIFZone zone, + SIFContext context, + RequestMetadata metadata, + ResponseParameters customResponseParams) + throws IllegalArgumentException, PersistenceException, SIFException; /*-----------------------------------------*/ @@ -257,8 +317,14 @@ public interface Provider extends DataModelLink * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is performed and the * message of the exceptions holds some info. * @throws DataTooLargeException If the data that shall be returned is too large due to the values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ - public HeaderProperties getServiceInfo(SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; + public HeaderProperties getServiceInfo(SIFZone zone, + SIFContext context, + PagingInfo pagingInfo, + RequestMetadata metadata) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; /*-------------------------------*/ /*-- Other required Operations --*/ diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryConsumer.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryConsumer.java index 1ecdc872..82668fbb 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryConsumer.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryConsumer.java @@ -22,7 +22,6 @@ import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; import sif3.common.exception.ServiceInvokationException; -import sif3.common.exception.UnsupportedQueryException; import sif3.common.header.HeaderValues.QueryIntention; import sif3.common.header.HeaderValues.RequestType; import sif3.common.model.CustomParameters; @@ -69,7 +68,6 @@ public interface QueryConsumer * @return A list of responses with one response for each zone/context combination. The data in the response is a "collection-type" style * object as defined in the data model (i.e. StudentPersonals). * - * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions holds some info. * @throws ServiceInvokationException Service on provider could not be executed. See error log for details. */ @@ -78,7 +76,7 @@ public List retrieveByServicePath(QueryCriteria queryCriteria, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, - CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; + CustomParameters customParameters) throws PersistenceException, ServiceInvokationException; /** * This method is used to retrieve data based on the 'Query By Example' (QBE) concept. All objects that match the @@ -106,7 +104,6 @@ public List retrieveByServicePath(QueryCriteria queryCriteria, * * @throws PersistenceException Persistence Store could not be accessed successfully. An error log entry is * performed and the message of the exceptions holds some info. - * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) * @throws DataTooLargeException If the data that shall be returned is too large due to the query criteria or * values in the paging info. */ @@ -115,5 +112,5 @@ public List retrieveByQBE(Object exampleObject, List zoneCtxList, RequestType requestType, QueryIntention queryIntention, - CustomParameters customParameters) throws PersistenceException, UnsupportedQueryException, ServiceInvokationException; + CustomParameters customParameters) throws PersistenceException, ServiceInvokationException; } \ No newline at end of file diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryProvider.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryProvider.java index c1f0a1cf..3a0dfce3 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryProvider.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/interfaces/QueryProvider.java @@ -2,7 +2,7 @@ * QueryProvider.java * Created: 05/01/2015 * - * Copyright 2015 Systemic Pty Ltd + * Copyright 2015-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import sif3.common.exception.DataTooLargeException; import sif3.common.exception.PersistenceException; +import sif3.common.exception.SIFException; import sif3.common.exception.UnsupportedQueryException; import sif3.common.model.PagingInfo; import sif3.common.model.QueryCriteria; @@ -60,13 +61,16 @@ public interface QueryProvider extends Provider * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) * @throws DataTooLargeException If the data that shall be returned is too large due to the query criteria or * values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ public Object retrieveByServicePath(QueryCriteria queryCriteria, SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata, - ResponseParameters customResponseParams) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; + ResponseParameters customResponseParams) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; /** * This method is used to retrieve data based on the 'Query By Example' (QBE) concept. All objects that match the @@ -91,11 +95,14 @@ public Object retrieveByServicePath(QueryCriteria queryCriteria, * @throws UnsupportedQueryException The query provided with this request is not supported (NOT YET IMPLEMENTED FUNCTIONALITY) * @throws DataTooLargeException If the data that shall be returned is too large due to the query criteria or * values in the paging info. + * @throws SIFException Any other exception the implementation of that class wants to throw. This will be translated into proper + * SIF Error message that will be returned to the consumer. */ public Object retrieveByQBE(Object exampleObject, SIFZone zone, SIFContext context, PagingInfo pagingInfo, RequestMetadata metadata, - ResponseParameters customResponseParams) throws PersistenceException, UnsupportedQueryException, DataTooLargeException; + ResponseParameters customResponseParams) + throws PersistenceException, UnsupportedQueryException, DataTooLargeException, SIFException; } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ACL.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ACL.java new file mode 100644 index 00000000..c41f9437 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ACL.java @@ -0,0 +1,81 @@ +/* + * ACL.java + * Created: 11 Jan 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.model; + +import java.io.Serializable; +import java.util.HashMap; + +import sif3.common.CommonConstants.RightType; + +/** + * @author Joerg Huber + * + */ +public abstract class ACL implements Serializable +{ + private static final long serialVersionUID = -7589657216577175353L; + + public enum AccessType {APPROVED, SUPPORTED, REJECTED, REQUESTED, UNSUPPORTED}; + + public enum AccessRight {QUERY, CREATE, UPDATE, DELETE, PROVIDE, SUBSCRIBE, ADMIN}; + + private HashMap rights = new HashMap(); + + public abstract RightType getRightType(); + + public HashMap getRights() + { + return this.rights; + } + + public void setRights(HashMap rights) + { + this.rights = rights; + } + + public void addRight(AccessRight right, AccessType accessType) + { + rights.put(right, accessType); + } + + public boolean hasRight(AccessRight right, AccessType accessType) + { + AccessType accessExists = rights.get(right); + if (accessExists != null) + { + return accessExists == accessType; + } + else + { + return false; + } + } + + public AccessType getAccessType(AccessRight right) + { + return rights.get(right); + } + + @Override + public String toString() + { + return "ACL [rights=" + rights + ", getRightType()=" + getRightType() + "]"; + } + + +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseMetadata.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseMetadata.java index 9bf91823..b400da7a 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseMetadata.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseMetadata.java @@ -151,6 +151,16 @@ public String getRequestParameter(String paramterName) } return value; } + + /** + * Returns the set of URL Query Parameters and HTTP Headers in their raw form. + * + * @return See desc. + */ + public RequestParameters getRequestParameters() + { + return requestParameters; + } @Override public String toString() diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseRequestParameter.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseRequestParameter.java new file mode 100644 index 00000000..6ca4db7e --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/BaseRequestParameter.java @@ -0,0 +1,258 @@ +/* + * BaseRequestParameter.java + * Created: 22 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.model; + +import java.io.Serializable; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.header.HeaderProperties; + +/** + * Most consumer and provider end-points use a given set of parameters such as zone, context, HTTP headers and URL query parameters. This class + * encapsulates the set of parameters. It allows to define interfaces, classes etc with methods that use this encapsulated parameters rather than + * having a large list of individual parameters. It is intended that this class will replace the class RequestParameter that is beiong used at the moment + * + * + * @author Joerg Huber + * + */ +public class BaseRequestParameter extends RequestParameters implements Serializable +{ + private static final long serialVersionUID = -725442773873032692L; + + private SIFZone zone = null; + private SIFContext context = null; + private URLQueryParameter queryParams = new URLQueryParameter(); + private HeaderProperties httpHeaderParams = new HeaderProperties(); + + public BaseRequestParameter() + { + this(null, null, null, null); + } + + public BaseRequestParameter(SIFZone zone, SIFContext context, URLQueryParameter queryParams, HeaderProperties httpHeaderParams) + { + super(); + setHttpHeaderParams(httpHeaderParams); + setQueryParams(queryParams); + setZone(zone); + setContext(context); + } + + public URLQueryParameter getQueryParams() + { + return this.queryParams; + } + + public SIFZone getZone() + { + return zone; + } + + public void setZone(SIFZone zone) + { + this.zone = zone; + } + + public SIFContext getContext() + { + return context; + } + + public void setContext(SIFContext context) + { + this.context = context; + } + + /** + * This method sets the URL query parameters that are managed within this class. If null is passed in and there are + * already query parameters set through calls of other methods within this class then all URL query parameters are + * removed. If the queryParams is not null then the current URL query parameters are entirely replaced with the given + * queryParams. + * + * @param queryParams See desc. + */ + public void setQueryParams(URLQueryParameter queryParams) + { + if (queryParams != null) + { + this.queryParams = queryParams; + } + else + { + this.queryParams = new URLQueryParameter(); + } + } + + public HeaderProperties getHttpHeaderParams() + { + return this.httpHeaderParams; + } + + /** + * This method sets the HTTP header parameters that are managed within this class. If null is passed in and there are + * already HTTP header parameters set through calls of other methods within this class then all HTTP header parameters + * are removed. If the httpHeaderParams is not null then the current HTTP header parameters are entirely replaced with + * the given httpHeaderParams. + * + * @param httpHeaderParams The HTTP headers to be used within this class. See desc. + */ + public void setHttpHeaderParams(HeaderProperties httpHeaderParams) + { + if (httpHeaderParams != null) + { + this.httpHeaderParams = httpHeaderParams; + } + else + { + this.httpHeaderParams = new HeaderProperties(); + } + } + + /** + * This method adds the given parameter and value to the list of URL query parameters. If there is already a parameter + * with that name then it will be overridden. The parameterName is case sensitive, so 'Param1' and 'param1' as the + * parameter name are treated as two different parameters. This behaviour matches the HTTP specification. If any of the + * two parameters is empty or null then they are not added to the list of URL query parameters. + * + * @param parameterName The name of the URL query parameter. + * @param value The value of the URL query parameter. + */ + public void addURLQueryParameter(String parameterName, String value) + { + if (StringUtils.notEmpty(parameterName) && StringUtils.notEmpty(value)) + { + getQueryParams().setQueryParam(parameterName, value); + } + } + + /** + * This method will remove the given URL query parameter from the list of URL query parameters. If there is no such + * parameter in the current list or the parameterName is null then no action is taken. The parameterName is case + * sensitive. + * + * @param parameterName The name of the parameter to be removed. + */ + public void removeURLQueryParameter(String parameterName) + { + getQueryParams().removeQueryParam(parameterName); //null check is done in the removeQueryParam() method + } + + /** + * This method returns the value of the given URL query parameter as a string. If no URL query parameter with that name + * exists then null is returned. The parameterName is case sensitive. + * + * @param parameterName Name of the URL query parameter for which the value shall be returned. + * + * @return See desc. + */ + public String getURLQueryParameter(String parameterName) + { + return getQueryParams().getQueryParam(parameterName); //null check is done in the getQueryParam() method + } + + /** + * This method return true if a given URL query parameter is already part of the list. If it doesn't exist then false + * is returned. + * + * @param parameterName The URL query parameter to check for. + * + * @return See desc. + */ + public boolean existURLQueryParameter(String parameterName) + { + return getURLQueryParameter(parameterName) != null; + } + + /** + * This method adds the given parameter and value to the list of HTTP header parameters. If there is already a parameter + * with that name then it will be overridden. The parameterName is case insensitive, so 'Param1' and 'param1' as the + * parameter name are treated as the same parameters. This behaviour matches the HTTP specification. If any of the + * two parameters is empty or null then they are not added to the list of URL query parameters. + * + * @param parameterName The name of the HTTP header parameter. + * @param value The value of the HTTP header parameter. + */ + public void addHTTPHeaderParameter(String parameterName, String value) + { + getHttpHeaderParams().setHeaderProperty(parameterName, value); + } + + /** + * This method will remove the given HTTP header parameter from the list of HTTP headers. If there is no such parameter + * in the current list or the parameterName is null then no action is taken. The parameterName is case insensitive. + * + * @param parameterName The name of the parameter to be removed. + */ + public void removeHTTPHeaderParameter(String parameterName) + { + getHttpHeaderParams().setHeaderProperty(parameterName, null); + } + + /** + * This method returns the value of the given HTTP header field as a string. If no HTTP header field with that name + * exists then null is returned. The parameterName is case insensitive. + * + * @param parameterName Name of the HTTP header field for which the value shall be returned. + * + * @return See desc. + */ + public String getHTTPParameter(String parameterName) + { + return getHttpHeaderParams().getHeaderProperty(parameterName); + } + + /** + * This method return true if a given HTTP header parameter is already part of the list. If it doesn't exist then false + * is returned. + * + * @param parameterName The HTTP header parameter to check for. + * + * @return See desc. + */ + public boolean existHTTPParameter(String parameterName) + { + return getHTTPParameter(parameterName) != null; + } + + /** + * This method returns the value of the given parameter as a String. This value can either be in a HTTP header field + * or a URL query parameter. If a value exists in both (URL query parameter and HTTP header) then the HTTP header will + * take precedence and be returned. If the parameter does not exist then null is returned. + * + * @param parameterName Name of the HTTP header or URL query parameter for which the value shall be returned. + * + * @return See desc. + */ + public String getRequestParameter(String parameterName) + { + String value = getHTTPParameter(parameterName); + if (value == null) + { + value = getURLQueryParameter(parameterName); + } + return value; + } + + @Override + public String toString() + { + return "BaseRequestParameter [zone=" + zone + ", context=" + context + ", queryParams=" + + queryParams + ", httpHeaderParams=" + httpHeaderParams + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PagingInfo.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PagingInfo.java index 1f90e85e..00b5cf19 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PagingInfo.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PagingInfo.java @@ -66,7 +66,7 @@ public PagingInfo(int pageSize) * Constructor * * @param pageSize The number of object per page - * @param currentPageNo The number/index of the page to be returned. Starts with 0! + * @param currentPageNo The number/index of the page to be returned. Starts with 1! */ public PagingInfo(int pageSize, int currentPageNo) { @@ -78,7 +78,7 @@ public PagingInfo(int pageSize, int currentPageNo) * Constructor * * @param pageSize The number of object per page - * @param currentPageNo The number/index of the page to be returned. Starts with 0! + * @param currentPageNo The number/index of the page to be returned. Starts with 1! * @param totalObjects The total number of objects available for the 'query': count(*) */ public PagingInfo(int pageSize, int currentPageNo, int totalObjects) @@ -92,7 +92,7 @@ public PagingInfo(int pageSize, int currentPageNo, int totalObjects) * Constructor * * @param pageSize The number of object per page - * @param currentPageNo The number/index of the page to be returned. Starts with 0! + * @param currentPageNo The number/index of the page to be returned. Starts with 1! * @param totalObjects The total number of objects available for the 'query': count(*) * @param navigationId The navigation ID as returned by a provider in the HTTP response. */ @@ -223,9 +223,9 @@ public int getCurrentPageNo() } /** - * Set the page to be returned. Starts with 0, so to return the first page set this value to 0. + * Set the page to be returned. Starts with 1, so to return the first page set this value to 1. * - * @param currentPageNo See desc. Default = 0. + * @param currentPageNo See desc. Default = 1. */ public void setCurrentPageNo(int currentPageNo) { @@ -243,7 +243,7 @@ public void setNavigationId(String navigationId) } /** - * This method returns the total number of pages based on the page size and the total number objects. If either if these two values is negative + * This method returns the total number of pages based on the page size and the total number objects. If either of these two values is negative * then it indicates that they are not defined and NOT_DEFINED (-1) is returned. If both of these values are given then the appropriate max * number of pages value is returned. * diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseACL.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseACL.java new file mode 100644 index 00000000..f2ed2b8a --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseACL.java @@ -0,0 +1,144 @@ +/* + * PhaseInfo.java + * Created: 11/01/2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.common.model; + +import java.io.Serializable; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; + +/** + * This class is a POJO for phase information of a functional service as provided by the Job. Each Job has a list of phases and each phase + * has a number of properties that define a phase such as name, ACLS etc. These properties can be stored in this class and utilised in + * higher level classes of this framework. + * + * @author Joerg Huber + * + */ +public class PhaseACL implements Serializable +{ + private static final long serialVersionUID = 8044591386912190668L; + + private String phaseName = null; + private PhaseRights phaseRights = new PhaseRights(); + private StateRights stateRights = new StateRights(); + + /** + * Constructor + * + * @param phaseName The name of a phase within a functional service. + */ + public PhaseACL(String phaseName) + { + super(); + setPhaseName(phaseName); + } + + public String getPhaseName() + { + return this.phaseName; + } + + public void setPhaseName(String phaseName) + { + this.phaseName = phaseName; + } + + public PhaseRights getPhaseRights() + { + return phaseRights; + } + + public void setPhaseRights(PhaseRights phaseRights) + { + this.phaseRights = phaseRights; + } + + public StateRights getStateRights() + { + return stateRights; + } + + public void setStateRights(StateRights stateRights) + { + this.stateRights = stateRights; + } + + /* Convenience method */ + public void setPhaseRight(AccessRight right, AccessType accessType) + { + getPhaseRights().addRight(right, accessType); + } + + /* Convenience method */ + public void setStateRight(AccessRight right, AccessType accessType) + { + getStateRights().addRight(right, accessType); + } + + /* Convenience method: True: Success, False: Failed (i.e. invalid right or access type */ + public boolean setStateRight(String right, String accessType) + { + return setRight(right, accessType, Boolean.TRUE); + } + + /* Convenience method: True: Success, False: Failed (i.e. invalid right or access type */ + public boolean setPhaseRight(String right, String accessType) + { + return setRight(right, accessType, Boolean.FALSE); + } + + @Override + public String toString() + { + return "PhaseInfo [phaseName=" + phaseName + ", phaseRights=" + phaseRights + + ", stateRights=" + stateRights + "]"; + } + + /*--------------------*/ + /*-- Private method --*/ + /*--------------------*/ + private boolean setRight(String right, String accessType, boolean isStateRight) + { + if (StringUtils.notEmpty(right) && StringUtils.notEmpty(accessType)) + { + try + { + if (isStateRight) + { + setStateRight(AccessRight.valueOf(right.toUpperCase()), AccessType.valueOf(accessType.toUpperCase())); + } + else + { + setPhaseRight(AccessRight.valueOf(right.toUpperCase()), AccessType.valueOf(accessType.toUpperCase())); + } + return true; + } + catch (Exception ex) + { + return false; + } + } + else + { + return false; + } + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseRights.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseRights.java new file mode 100644 index 00000000..b3d4ff6c --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/PhaseRights.java @@ -0,0 +1,44 @@ +/* + * PhaseRights.java + * Created: 11/01/2013 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.common.model; + +import sif3.common.CommonConstants.RightType; + +/** + * @author Joerg Huber + * + */ +public class PhaseRights extends ACL +{ + private static final long serialVersionUID = 107494441323318L; + + public PhaseRights() + { + super(); + } + + /* (non-Javadoc) + * @see sif3.common.model.ACL#getRightType() + */ + @Override + public RightType getRightType() + { + return RightType.PHASE; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceInfo.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceInfo.java index df84b24d..13b8e116 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceInfo.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceInfo.java @@ -20,10 +20,10 @@ import java.io.Serializable; -import sif3.common.header.HeaderValues.ServiceType; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; /** * This class is a POJO for service information as provided by the Environment. Each environment has a list of services and each service diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceRights.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceRights.java index c10b964b..3d40bb5f 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceRights.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/ServiceRights.java @@ -2,7 +2,7 @@ * ServiceRights.java * Created: 30/12/2013 * - * Copyright 2013 Systemic Pty Ltd + * Copyright 2013-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,31 @@ package sif3.common.model; -import java.io.Serializable; -import java.util.HashMap; +import sif3.common.CommonConstants.RightType; /** * @author Joerg Huber * */ -public class ServiceRights implements Serializable +public class ServiceRights extends ACL { + private static final long serialVersionUID = -2047513444161793310L; + + public ServiceRights() + { + super(); + } + + /* (non-Javadoc) + * @see sif3.common.model.ACL#getRightType() + */ + @Override + public RightType getRightType() + { + return RightType.SERVICE; + } + + /* private static final long serialVersionUID = 2972634204650446881L; public enum AccessType {APPROVED, SUPPORTED, REJECTED, REQUESTED, UNSUPPORTED }; @@ -74,5 +90,5 @@ public String toString() return "ServiceRights [rights=" + this.rights + "]"; } - + */ } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/StateRights.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/StateRights.java new file mode 100644 index 00000000..a6dea9e3 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/StateRights.java @@ -0,0 +1,44 @@ +/* + * StateRights.java + * Created: 11/01/2013 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.common.model; + +import sif3.common.CommonConstants.RightType; + +/** + * @author Joerg Huber + * + */ +public class StateRights extends ACL +{ + private static final long serialVersionUID = 107494441323318L; + + public StateRights() + { + super(); + } + + /* (non-Javadoc) + * @see sif3.common.model.ACL#getRightType() + */ + @Override + public RightType getRightType() + { + return RightType.STATE; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/URIPathInfo.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/URIPathInfo.java index 46465b3e..482f5026 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/URIPathInfo.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/URIPathInfo.java @@ -1,7 +1,7 @@ /* * URIPathInfo.java Created: 1 Dec 2015 * - * Copyright 2015 Systemic Pty Ltd + * Copyright 2015-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,9 @@ import java.io.Serializable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import au.com.systemic.framework.utils.StringUtils; import sif3.common.CommonConstants; import sif3.common.header.HeaderValues.ServiceType; @@ -31,6 +34,8 @@ * * Object Service: /SchoolInfos/1234;zoneId=ABC
* Service Path: /SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFAULT

+ * Job Object: /StudentRollover;zoneId=ABC;contextId=DEFUALT

+ * Job Phase: /StudentRollover//;zoneId=ABC;contextId=DEFUALT

* * @author Joerg Huber * @@ -39,11 +44,14 @@ public class URIPathInfo implements Serializable { private static final long serialVersionUID = -4769833196778943230L; + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private String originalURLString = null; - private String serviceName = null; // Service Path => school/{}/students + private String serviceName = null; // Service Path => school/{}/students; Job & Object service it will be first segment + private String phaseName = null; // Only applicable for Functional Services. private ServiceType serviceType = null; private String urlService = null; // Service Path => school/1224/students - private String resourceID = null; + private String resourceID = null; // refId of object or job private SIFZone zone = null; private SIFContext context = CommonConstants.DEFAULT_CONTEXT; private URLQueryParameter queryParams = new URLQueryParameter(); @@ -51,11 +59,32 @@ public class URIPathInfo implements Serializable /* * Typical example can be: - * /SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFUALT + * Servicepath: /SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFUALT + * Job Object: /StudentRollover;zoneId=ABC;contextId=DEFUALT + * Job Phase: /StudentRollover//;zoneId=ABC;contextId=DEFUALT + * + * Because ServicePath and JobPhase paths look structurally the same the "serviceType" parameter is required to decide where + * values of the segment should be stored. If serviceType is empty or null "OBJECT" is assumed as per SIF Specification. */ - public URIPathInfo(String relURLNoProtocol) + public URIPathInfo(String relURLNoProtocol, String serviceTypeStr) { originalURLString = new String(relURLNoProtocol); + if (StringUtils.notEmpty(serviceTypeStr)) + { + try + { + this.serviceType = ServiceType.valueOf(serviceTypeStr.toUpperCase()); + } + catch (Exception ex) + { + logger.error("Found invalid service type in request header. Assume OBJECT."); + this.serviceType = ServiceType.OBJECT; + } + } + else + { + this.serviceType = ServiceType.OBJECT; + } // Split on '?' to extract the URL Query parameters String[] components = relURLNoProtocol.split("\\?"); @@ -105,6 +134,11 @@ public String getServiceName() return serviceName; } + public String getPhaseName() + { + return phaseName; + } + public ServiceType getServiceType() { return serviceType; @@ -133,11 +167,10 @@ public URLQueryParameter getQueryParams() @Override public String toString() { - return "URIPathInfo [originalURLString=" + originalURLString - + ", serviceName=" + serviceName + ", serviceType=" - + serviceType + ", urlService=" + urlService + ", resourceID=" - + resourceID + ", zone=" + zone + ", context=" + context - + ", queryParams=" + queryParams + "]"; + return "URIPathInfo [originalURLString=" + originalURLString + ", serviceName=" + + serviceName + ", phaseName=" + phaseName + ", serviceType=" + serviceType + + ", urlService=" + urlService + ", resourceID=" + resourceID + ", zone=" + zone + + ", context=" + context + ", queryParams=" + queryParams + "]"; } private void setZoneOrContext(String zoneOrContextID) @@ -155,27 +188,36 @@ private void setZoneOrContext(String zoneOrContextID) private void processServiceName(String segmentStr) { - // Check if we have more than one segment => ServicePath! + // Check if we have more than 3+ segments => ServicePath (3, 5, 7 etc segments) or Job Phase (exactly 3 segments)! String[] segments = segmentStr.split("/"); - if (segments.length > 2) + if (segments.length > 2) // not an object service { - serviceName = ""; - for (int i = 0; i < segments.length - 2; i += 2) + if (getServiceType() == ServiceType.SERVICEPATH) { - serviceName = serviceName + segments[i] + "/{}/"; - + serviceName = ""; + for (int i = 0; i < segments.length - 2; i += 2) + { + serviceName = serviceName + segments[i] + "/{}/"; + + } + serviceName = serviceName + segments[segments.length - 1]; +// serviceType = ServiceType.SERVICEPATH; + } + else // Job Phase + { + serviceName = segments[0]; + resourceID = segments[1]; + phaseName = segments[2]; } - serviceName = serviceName + segments[segments.length - 1]; - serviceType = ServiceType.SERVICEPATH; } - else + else // one or two segments is Object or Job (not job phase though!) { serviceName = segments[0]; if (segments.length == 2) { resourceID = segments[1]; } - serviceType = ServiceType.OBJECT; +// serviceType = ServiceType.OBJECT; } } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/delayed/DelayedBaseReceipt.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/delayed/DelayedBaseReceipt.java index 06473e85..24ae8e1d 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/delayed/DelayedBaseReceipt.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/delayed/DelayedBaseReceipt.java @@ -49,7 +49,13 @@ public class DelayedBaseReceipt implements Serializable // The Service Name of the request private String serviceName = null; - // The serviceType (OBJECT, SERVICEPATH etc). + // The delayed response to a functional service phase must hold the jobID which is the resourceID + private String resourceID = null; + + // The delayed response to a functional service phase must hold the phase name + private String phaseName = null; + + // The serviceType (OBJECT, SERVICEPATH, FUNCTIONAL etc). private ServiceType serviceType = ServiceType.OBJECT; // Holds the type originally requested operation (i.e. CREATE, QUERY ...) @@ -119,6 +125,26 @@ public void setServiceName(String serviceName) this.serviceName = serviceName; } + public String getResourceID() + { + return resourceID; + } + + public void setResourceID(String resourceID) + { + this.resourceID = resourceID; + } + + public String getPhaseName() + { + return phaseName; + } + + public void setPhaseName(String phaseName) + { + this.phaseName = phaseName; + } + /** * @return the serviceType */ @@ -148,10 +174,10 @@ public void setRequestedAction(ResponseAction requestedAction) @Override public String toString() { - return "DelayedBaseReceipt [requestGUID=" + requestGUID + ", zone=" - + zone + ", context=" + context + ", serviceName=" - + serviceName + ", serviceType=" + serviceType - + ", requestedAction=" + requestedAction + "]"; + return "DelayedBaseReceipt [requestGUID=" + requestGUID + ", zone=" + zone + ", context=" + + context + ", serviceName=" + serviceName + ", resourceID=" + resourceID + + ", phaseName=" + phaseName + ", serviceType=" + serviceType + ", requestedAction=" + + requestedAction + "]"; } } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/JobCreateRequestParameter.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/JobCreateRequestParameter.java new file mode 100644 index 00000000..85e985a5 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/JobCreateRequestParameter.java @@ -0,0 +1,95 @@ +/* + * JobRequestParameter.java + * Created: 22 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.model.job; + +import java.io.Serializable; + +import org.w3c.dom.Element; + +/** + * As part of creating a job by a consumer the consumer can pass some "initialising" values to the job. The initial values + * relate to a particular phase and can hold any data structure. The "any data structure" is indicated by an xs:anyType in + * the infrastructure data model for a job. + * Because this framework uses JAXB as its underlying serialiser for the infrastructure data model the xs:anyType is encapsulated + * into a org.w3c.dom.Element. This class abstracts the initialisation part of the Job object accordingly. + * + * The "unfortunate" side effect of having an xs:anyType in the initialisation section of a Job object is that consumers and + * providers have to programmatically inspect the Element to extract actual parameter data. The framework cannot further + * support that as an xs:anyType can be of any structure and depends entirely on the implementation of the functional service + * what that structure is. The framework is agnostic to this initialisation structure. + * + * @author Joerg Huber + */ +public class JobCreateRequestParameter implements Serializable +{ + private static final long serialVersionUID = -2397727515320241805L; + + private String jobID = null; + private String initPhaseName = null; +// private List initParams = null; + private Element initParams = null; + + public JobCreateRequestParameter() + { + this(null, null); + } + + public JobCreateRequestParameter(String initPhaseName, Element initParams) + { + super(); + setInitParams(initParams); + setInitPhaseName(initPhaseName); + } + + public String getJobID() + { + return jobID; + } + + public void setJobID(String jobID) + { + this.jobID = jobID; + } + + public String getInitPhaseName() + { + return initPhaseName; + } + + public void setInitPhaseName(String initPhaseName) + { + this.initPhaseName = initPhaseName; + } + + public Element getInitParams() + { + return initParams; + } + + public void setInitParams(Element initParams) + { + this.initParams = initParams; + } + + @Override + public String toString() + { + return "JobCreateRequestParameter [jobID=" + jobID + ", initPhaseName=" + initPhaseName + + ", initParams=" + initParams + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/PhaseInfo.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/PhaseInfo.java new file mode 100644 index 00000000..c7398129 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/model/job/PhaseInfo.java @@ -0,0 +1,77 @@ +/* + * PhaseInfo.java + * Created: 19 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.model.job; + +import java.io.Serializable; + +/** + * @author Joerg Huber + * + */ +public class PhaseInfo implements Serializable +{ + private static final long serialVersionUID = 5445852424920539917L; + + private String jobID = null; + private String phaseName = null; + + public PhaseInfo() + { + this(null, null); + } + + public PhaseInfo(String jobID) + { + this(jobID, null); + } + + public PhaseInfo(String jobID, String phaseName) + { + super(); + setJobID(jobID); + setPhaseName(phaseName); + } + + + public String getJobID() + { + return jobID; + } + + public void setJobID(String jobID) + { + this.jobID = jobID; + } + + public String getPhaseName() + { + return phaseName; + } + + public void setPhaseName(String phaseName) + { + this.phaseName = phaseName; + } + + @Override + public String toString() + { + return "PhaseInfo [jobID=" + jobID + ", phaseName=" + phaseName + "]"; + } + +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobDAO.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobDAO.java new file mode 100644 index 00000000..456d4628 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobDAO.java @@ -0,0 +1,968 @@ +/* + * JobDAO.java + * Created: 6 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.persist.dao; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.HibernateException; +import org.hibernate.Query; +import org.hibernate.criterion.Order; +import org.hibernate.criterion.Restrictions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.DateUtils; +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.CommonConstants.JobState; +import sif3.common.exception.PersistenceException; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.PagingInfo; +import sif3.common.persist.common.BasicTransaction; +import sif3.common.persist.model.SIF3Job; +import sif3.common.persist.model.SIF3JobEvent; +import sif3.common.persist.model.SIF3JobEvent.JobEventType; + +/** + * @author Joerg Huber + * + */ +public class JobDAO extends BaseDAO +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + //-------------------------// + //-- JOB related methods --// + //-------------------------// + public SIF3Job getJob(BasicTransaction tx, long internalID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + try + { + return (SIF3Job)tx.getSession().get(SIF3Job.class, internalID); + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job for internal ID = '"+ internalID + "'.", e); + } + } + + public SIF3Job getJobByUUID(BasicTransaction tx, String uuid, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + if (StringUtils.isEmpty(uuid)) + { + throw new IllegalArgumentException("uuid empty or null."); + } + + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(SIF3Job.class) + .add(Restrictions.eq("jobID", uuid)) + .add(Restrictions.eq("adapterType", adapterType.name())); + + @SuppressWarnings("unchecked") + List jobs = criteria.list(); + + // There can only be a maximum of one + if (jobs.isEmpty()) // no job for given uuid and adapter type + { + logger.debug("No Job for UUID = '"+ uuid + "' and adapterType = '" + adapterType + "' exists in SIF3_JOB table."); + return null; + } + else // already exists + { + return jobs.get(0); + } + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job for uuid = '"+ uuid + " and adapterType = '" + adapterType + "'.", e); + } + } + + /* + * Ensure that not null is returned. Either throw exception or a non-null list must be returned. + */ + public List retrieveJobs(BasicTransaction tx, String serviceName, String fingerprint, String zoneID, String contextID, PagingInfo pagingInfo, AdapterType adapterType) throws PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(SIF3Job.class); + + if (StringUtils.notEmpty(serviceName)) + { + criteria = criteria.add(Restrictions.eq("serviceName", serviceName)); + } + if (StringUtils.notEmpty(fingerprint)) + { + criteria = criteria.add(Restrictions.eq("fingerprint", fingerprint)); + } + if (StringUtils.notEmpty(zoneID)) + { + criteria = criteria.add(Restrictions.eq("zoneID", zoneID)); + } + if (StringUtils.notEmpty(contextID)) + { + criteria = criteria.add(Restrictions.eq("contextID", contextID)); + } + criteria = criteria.add(Restrictions.eq("adapterType", adapterType.name())); + + // We need pageSize and page number to get a 'window' view. + if (pagingInfo != null) + { + if (pagingInfo.getCurrentPageNo() < CommonConstants.FIRST_PAGE) // no really set, so we assume first page + { + pagingInfo.setCurrentPageNo(CommonConstants.FIRST_PAGE); + } + + if (pagingInfo.getPageSize() > 0) // Ok we have a page size. + { + criteria.setFirstResult((pagingInfo.getCurrentPageNo() - CommonConstants.FIRST_PAGE) * pagingInfo.getPageSize()); + criteria.setMaxResults(pagingInfo.getPageSize()); + } + } + + //Add orderBy to ensure consistent results + criteria.addOrder(Order.asc("internalID")); + + @SuppressWarnings("unchecked") + List jobs = criteria.list(); + + // Just in case test for null.... + if (jobs == null) + { + jobs = new ArrayList(); + } + + return jobs; + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job for fingerprint = '"+ fingerprint + ", zoneID = " + zoneID + ", contextID = "+ contextID + ", adapterType = '" + adapterType + "' and pagingInfo = "+pagingInfo+".", e); + } + } + + + /** + * This method will add a Job to the SIF3_JOB table. The Job must have a minimum set of data, otherwise an IllegalArgumentExceptionis thrown. + * The data required is listed below:

+ * - jobID + * - adapterType + * - serviceName: This must match the name as in the Service URL Name. + * - jobXML + * - Ideally: fingerprint if it is intended to allow only the originator to see information about the job + * - Ideally: currentState. If not set then it is defaulted to JobState.NOTSTARTED as defined by the SIF 3.x Spec. + * Further this value must be a value of the CommonConstants.JobState enumeration.

+ * + * The following values will be ignored and defaulted. The returned object will have them populated as followed:
+ * - internalID: Will automatically allocated. + * - created: Will be set to current date.
+ * - expireDatetime: Will be calculated based on the 'created' and the timeoutPeriod. If timeoutPeriod==null then the expireDatetime will + * not be set. Also if expireDatetime is set it will take precedence of the calculated value! + * timeoutPeriod must be the string representation of a javax.xml.datatype.Duration type. + * + * @param tx The current transaction. Cannot be null. + * @param job The job to be added to the SIF3_JOB table. + * + * @return The newly created job. This has now the internalID populated. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3Job createJob(BasicTransaction tx, SIF3Job job) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (job == null) + { + throw new IllegalArgumentException("Attempted to add a job with no data (i.e. job object is null)"); + } + + // There is a minimum set of data that is required for a job. If they are not provided an IllegalArgumentException is thrown. + String errorTxt = ""; + if (StringUtils.isEmpty(job.getJobID())) + { + errorTxt = errorTxt + "jobID is null or empty.\n"; + } + if (StringUtils.isEmpty(job.getServiceName())) + { + errorTxt = errorTxt + "job name is null or empty.\n"; + } +// if (StringUtils.isEmpty(job.getEnvironmentID())) +// { +// errorTxt = errorTxt + "environmentID is null or empty.\n"; +// } + if (job.getAdapterType() == null) + { + errorTxt = errorTxt + "adapterType is null.\n"; + } + if (job.getJobXML() == null) + { + errorTxt = errorTxt + "jobXML is required.\n"; + } + + if (errorTxt.length() > 0) + { + throw new IllegalArgumentException(errorTxt); + } + + // Default some values: + job.setCreated(new Date()); + + if (StringUtils.isEmpty(job.getCurrentState())) + { + job.setCurrentState(JobState.NOTSTARTED.name()); + } + + //Check if we need to calculate expire date + if (job.getExpireDatetime() == null) // if set it takes precedence and we don't calculate it. + { + //Check if Timeout Period is set. + if (job.getTimeoutPeriod() != null) + { + try + { + job.setExpireDatetime(DateUtils.addDuration(job.getTimeoutPeriod(), null)); + } + catch (ParseException ex) + { + // This is not good there is nothing we can do. Report error and throw a persistence exception + throw new PersistenceException(ex); + } + } + } + + // now we are ready to save it to the DB. + try + { + tx.getSession().save(job); + return job; + } + catch (Exception e) + { + throw new PersistenceException("Unable to create Job in SIF3_JOB table for "+ job +": "+e.getMessage(), e); + } + } + + /** + * Updates a job with the given values. Note that the internalID must be set and have the value of an existing object. The job object is + * updated with the exact values given. Some values are mandatory for it to be a valid object. They are listed below:

+ * - internalID: must be a value of an existing Job in the SIF3_JOB table. + * - jobID: + * - adapterType + * - seriveName: This must match the name as in the Service URL Name. + * - jobXML + * - Ideally: fingerprint if it is intended to allow only the originator to see information about the job

+ * - Ideally: currentState shall not be null. If it is it will be defaulted to JobState.NOTSTARTED

+ * + * The following values will be ignored and defaulted. The returned object will have them populated as followed:
+ * - lastModified: Will be set to current date.
+ * + * @param tx The current transaction. Cannot be null. + * @param job The job to be updated to the SIF3_JOB table. + * + * @return The updated job. This has a new lastModified date which is now the current date. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3Job updateJob(BasicTransaction tx, SIF3Job job) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (job == null) + { + logger.info("Attempted to updat a job with no data (i.e. job object is null). Ignore operation."); + } + + // There is a minimum set of data that is required for a job. If they are not provided an IllegalArgumentException is thrown. + String errorTxt = ""; + if (job.getInternalID() <= 0) //invalid! + { + errorTxt = errorTxt + "internalID must be a valid id > 0.\n"; + } + if (StringUtils.isEmpty(job.getJobID())) + { + errorTxt = errorTxt + "jobID is null or empty.\n"; + } + if (StringUtils.isEmpty(job.getServiceName())) + { + errorTxt = errorTxt + "job name is null or empty.\n"; + } +// if (StringUtils.isEmpty(job.getEnvironmentID())) +// { +// errorTxt = errorTxt + "environmentID is null or empty.\n"; +// } + if (job.getAdapterType() == null) + { + errorTxt = errorTxt + "adapterType is null.\n"; + } + if (job.getJobXML() == null) + { + errorTxt = errorTxt + "jobXML is required.\n"; + } + + if (errorTxt.length() > 0) + { + throw new IllegalArgumentException(errorTxt); + } + + // Default some values: + job.setLastModified(new Date()); + + // now we are ready to save it to the DB. + try + { + tx.getSession().update(job); + return job; + } + catch (Exception e) + { + throw new PersistenceException("Unable to update Job in SIF3_JOB table for "+ job +": "+e.getMessage(), e); + } + } + + public void removeJob(BasicTransaction tx, long internalID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (internalID <= 0) + { + logger.info("InternalID <= 0 for job. Ignore operation"); + } + + // now we are ready to save it to the DB. + try + { + SIF3Job job = new SIF3Job(internalID); + tx.getSession().delete(job); + } + catch (Exception e) + { + throw new PersistenceException("Unable to remove Job in SIF3_JOB table for intenalID = "+ internalID +": "+e.getMessage(), e); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the expire date is older than the current date. if no expire date is + * set then these jobs won't be removed. + * + * @param tx The current transaction. Cannot be null. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the tx or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeExpiredJobs(BasicTransaction tx, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + Date now = new Date(); + logger.debug("Remove Jobs that expired before or on "+now); + Query deleteStatement = tx.getSession().createQuery("delete from SIF3Job where expireDatetime is not null and expireDatetime <= :now and adapterType = :adapterType"); + deleteStatement = deleteStatement.setTimestamp("now", now).setString("adapterType", adapterType.name()); + + try + { + int numRowsDeleted = deleteStatement.executeUpdate(); + logger.debug(numRowsDeleted+" row(s) deleted from SIF3_JOB table."); + } + catch (HibernateException ex) + { + throw new PersistenceException(ex); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the job has not been updated since a given date. In other words the + * last update date & time is before the noUpdateSince date. This can be considered a stale job. They will be removed only if + * there is no expire date & time. If an expire date & time is available then the job won't be removed, regardless if it hasn't + * been updated since the given noUpdateSince. To remove expired jobs, use the removeExpiredJobs() method. + * + * @param tx The current transaction. Cannot be null. + * @param noUpdateSince The date for jobs to be removed that have not been updated since that date and do not have an expire date. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the tx or noUpdateSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeJobsWithoutChangeSince(BasicTransaction tx, Date noUpdateSince, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + if (noUpdateSince == null) + { + throw new IllegalArgumentException("noUpdateSince is null."); + } + + logger.debug("Remove Jobs that have not changed since "+noUpdateSince); + Query deleteStatement = tx.getSession().createQuery("delete from SIF3Job where expireDatetime is null and lastModified is not null and lastModified <= :noUpdateSince and adapterType = :adapterType"); + deleteStatement = deleteStatement.setTimestamp("noUpdateSince", noUpdateSince).setString("adapterType", adapterType.name()); + + try + { + int numRowsDeleted = deleteStatement.executeUpdate(); + logger.debug(numRowsDeleted+" row(s) deleted from SIF3_JOB table."); + } + catch (HibernateException ex) + { + throw new PersistenceException(ex); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the job has been created before the noUpdateSince date but has + * no last update date and no expire date. In other words the creation date & time is before the noUpdateSince date. + * This can be considered a stale job. They will be removed only if there is no expire and no last update date & time. + * If an expire or last update date & time is available then the job won't be removed, regardless if it has been created before + * the noUpdateSince. To remove expired jobs, use the removeExpiredJobs() method. To remove jobs that have last been updated\ + * before a given date use the removeJobsWithoutChangeSince() method. + * + * @param tx The current transaction. Cannot be null. + * @param noUpdateSince The date for jobs to be removed that have not been created before that date and do not have an expire date + * or last update date. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the tx or noUpdateSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeCreatedJobsWithoutChangeSince(BasicTransaction tx, Date noUpdateSince, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + if (noUpdateSince == null) + { + throw new IllegalArgumentException("noUpdateSince is null."); + } + + logger.debug("Remove Jobs that have been created before or on "+noUpdateSince+" but have never been updated."); + Query deleteStatement = tx.getSession().createQuery("delete from SIF3Job where expireDatetime is null and lastModified is null and created is not null and created <= :noUpdateSince and adapterType = :adapterType"); + deleteStatement = deleteStatement.setTimestamp("noUpdateSince", noUpdateSince).setString("adapterType", adapterType.name()); + + try + { + int numRowsDeleted = deleteStatement.executeUpdate(); + logger.debug(numRowsDeleted+" row(s) deleted from SIF3_JOB table."); + } + catch (HibernateException ex) + { + throw new PersistenceException(ex); + } + } + + //-------------------------------// + //-- JOB Event related methods --// + //------------------------------// + public SIF3JobEvent getJobEvent(BasicTransaction tx, long internalID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + try + { + return (SIF3JobEvent)tx.getSession().get(SIF3JobEvent.class, internalID); + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Event for internal ID = '"+ internalID + "'.", e); + } + } + + /* + * If any of the following parameters is null then it will be ignored as a "query parameter": + * - eventType + * - notYetPublished + * + * All other parameters are required. + * + * The list returned are events applicable for the given Job UUID and Adapter Type. + */ + @SuppressWarnings("unchecked") + public List getJobEventsByUUID(BasicTransaction tx, String uuid, AdapterType adapterType, EventAction eventType, Boolean notYetPublished) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + if (StringUtils.isEmpty(uuid)) + { + throw new IllegalArgumentException("uuid empty or null."); + } + + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(SIF3JobEvent.class) + .add(Restrictions.eq("jobID", uuid)) + .add(Restrictions.eq("adapterType", adapterType.name())); + + if (eventType != null) + { + criteria.add(Restrictions.eq("eventType", eventType.name().substring(0, 1))); + } + + if (notYetPublished != null) + { + criteria.add(Restrictions.eq("published", notYetPublished)); + } + + criteria.addOrder(Order.asc("eventDate")); + return criteria.list(); + + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Events for uuid = '"+ uuid + ", adapterType = '" + adapterType + "', Event Type = "+eventType + " and Not Yet Published = " + notYetPublished, e); + } + } + + /** + * This method will add a Job Event to the SIF3_JOB_EVENT table. The event is not sent as part of this event!
+ * The event must have a minimum set of data, otherwise an IllegalArgumentExceptionis thrown. The data required may depend on the + * event type as well. The minimum rules are listed below:

+ * - jobID + * - seriveName: This must match the name as in the Service URL Name. + * - adapterType + * - jobXML if event type is Update or Create. It can be null if the event type is Delete. + * - eventType + * - fullUpdate if event type is Update. + * - fingerprint if toFingerPrintOnly = true

+ * + * A few values will be defaulted if they are not set.
+ * - toFingerPrintOnly defaults to TRUE: Only the consumer with that fingerprint will receive the event. + * - consumerRequested defaults to TRUE: Event was caused by consumer requested operation on job. + * - fullUpdate defaults to TRUE: it is assumed that the entire Job Object is provided in case of an update event.

+ * + * The following values will be ignored and defaulted. The returned object will have them populated as followed:
+ * - internalID: Will automatically allocated. + * - eventDate: Current date. + * - published: FALSE + * - publishedDate: null.
+ * + * @param tx The current transaction. Cannot be null. + * @param jobEvent The event to be added to the SIF3_JOB_EVENT table. + * + * @return The newly created job event. This has now the internalID and the event date and published populated. The published will be set to FALSE + * and the event date will be set to current datetime. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3JobEvent createJobEvent(BasicTransaction tx, SIF3JobEvent jobEvent) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (jobEvent == null) + { + logger.info("Attempted to add a job event with no data (i.e. jobEvent object is null"); + } + + // There is a minimum set of data that is required for a job event. If they are not provided an IllegalArgumentException + // is thrown. + String errorTxt = ""; + if (StringUtils.isEmpty(jobEvent.getJobID())) + { + errorTxt = errorTxt + "jobID is null or empty.\n"; + } + if (StringUtils.isEmpty(jobEvent.getServiceName())) + { + errorTxt = errorTxt + "Service Name is null or empty.\n"; + } +// if (StringUtils.isEmpty(jobEvent.getEnvironmentID())) +// { +// errorTxt = errorTxt + "environmentID is null or empty.\n"; +// } + if (jobEvent.getAdapterType() == null) + { + errorTxt = errorTxt + "adapterType is null.\n"; + } + if (jobEvent.isToFingerPrintOnly()) + { + if (StringUtils.isEmpty(jobEvent.getFingerprint())) + { + errorTxt = errorTxt + "fingerprint is null or empty. Must be set because toFingerPrintOnly is set to TRUE.\n"; + } + } + if (StringUtils.isEmpty(jobEvent.getEventType())) + { + errorTxt = errorTxt + "eventType is null or empty.\n"; + } + else + { + if ("C".equalsIgnoreCase(jobEvent.getEventType())) + { + if (jobEvent.getJobXML() == null) + { + errorTxt = errorTxt + "jobXML is required for eventType = Create.\n"; + } + } + else if ("U".equalsIgnoreCase(jobEvent.getEventType())) + { + if (jobEvent.getJobXML() == null) + { + errorTxt = errorTxt + "jobXML is required for eventType = Update.\n"; + } + } + } + + if (errorTxt.length() > 0) + { + throw new IllegalArgumentException(errorTxt); + } + + // If we get here we have all minimum data. Default some of the values + jobEvent.setEventDate(new Date()); + jobEvent.setPublished(Boolean.FALSE); + jobEvent.setPublishedDate(null); + + // now we are ready to save it to the DB. + try + { + tx.getSession().save(jobEvent); + return jobEvent; + } + catch (Exception e) + { + throw new PersistenceException("Unable to create Job Event in SIF3_JOB_EVENT table for "+ jobEvent +": "+e.getMessage(), e); + } + } + + /** + * This method creates an event based on the given Job and teh other parameters. If the job.xml is empty then an event cannot really + * be created as crucial data is missing. The only exception is the delete event which only requires the job.id. There are a number of + * other pre-conditions to be met. They are listed in the createJobEvent(BasicTransaction, SIF3JobEvent) method. + * + * @param tx The current transaction. Cannot be null. + * @param job The job based on which an event shall be created. If null then no event is created and null is returned. + * @param eventType The event type (CREATE, UPDATE, DELETE). + * @param fingerprintOnly Indicating if the event is for the 'fingerprint' consumer only. + * @param consumerRequested Indicating if the event is caused by consumer requested operation on job. + * + * @return The newly created job event. This has now the internalID and the event date and published populated. + * The published will be set to FALSE and the event date will be set to current datetime. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3JobEvent createJobEvent(BasicTransaction tx, SIF3Job job, EventAction eventType, boolean fingerprintOnly, boolean consumerRequested) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + if (job != null) + { + return createJobEvent(tx, new SIF3JobEvent(job, eventType, fingerprintOnly, consumerRequested)); + } + else + { + return null; + } + } + + /** + * This method removes all rows in the SIF3_JOB_EVENT table that are older than the given date (inclusive). + * + * @param tx The current transaction. Cannot be null. + * @param olderThanDate The date to use. All entries older than this date will removed. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the tx, olderThanDate or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeJobEvents(BasicTransaction tx, Date olderThanDate, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (olderThanDate == null) + { + throw new IllegalArgumentException("olderThanDate is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + logger.debug("Remove Job Events created before or on "+olderThanDate+"."); + String hql = "delete from SIF3JobEvent where eventDate <= :olderThan and adapterType = :adapterType"; + try + { + int numRowsDeleted = tx.getSession().createQuery(hql).setTimestamp("olderThan", olderThanDate).setString("adapterType", adapterType.name()).executeUpdate(); + logger.debug(numRowsDeleted+" row(s) deleted from SIF3_JOB_EVENT table."); + } + catch (HibernateException ex) + { + throw new PersistenceException(ex); + } + } + + public void markJobEventAsPublished(BasicTransaction tx, long internalID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + SIF3JobEvent jobEvent = getJobEvent(tx, internalID); + if (jobEvent != null) + { + jobEvent.setPublished(Boolean.TRUE); + jobEvent.setPublishedDate(new Date());; + tx.getSession().update(jobEvent); + } + } + + public void removeJobEvent(BasicTransaction tx, long internalID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (internalID <= 0) + { + logger.info("InternalID <= 0 for jobEvent. Ignore operation"); + } + + try + { + SIF3JobEvent jobEvent = new SIF3JobEvent(internalID); + tx.getSession().delete(jobEvent); + } + catch (Exception e) + { + throw new PersistenceException("Unable to remove Job Event in SIF3_JOB_EVENT table for intenalID = "+ internalID +": "+e.getMessage(), e); + } + } + + /* + * Ensure that not null is returned. Either throw exception or a non-null list must be returned. + * Returns all Job Events since the given date. Only a "window" of rows are returned indicated with the pagingInfo + * parameter. + */ + public List retrieveJobEventsSince(BasicTransaction tx, Date changesSince, String serviceName, String fingerprint, String zoneID, String contextID, PagingInfo pagingInfo, AdapterType adapterType) throws PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + if (changesSince == null) + { + throw new IllegalArgumentException("changesSince is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(SIF3JobEvent.class); + + criteria = criteria.add(Restrictions.ge("eventDate", changesSince)); + + if (StringUtils.notEmpty(serviceName)) + { + criteria = criteria.add(Restrictions.eq("serviceName", serviceName)); + } + if (StringUtils.notEmpty(fingerprint)) + { + criteria = criteria.add(Restrictions.eq("fingerprint", fingerprint)); + } + if (StringUtils.notEmpty(zoneID)) + { + criteria = criteria.add(Restrictions.eq("zoneID", zoneID)); + } + if (StringUtils.notEmpty(contextID)) + { + criteria = criteria.add(Restrictions.eq("contextID", contextID)); + } + criteria = criteria.add(Restrictions.eq("adapterType", adapterType.name())); + + // We need pageSize and page number to get a 'window' view. + if (pagingInfo != null) + { + if (pagingInfo.getCurrentPageNo() < CommonConstants.FIRST_PAGE) // no really set, so we assume first page + { + pagingInfo.setCurrentPageNo(CommonConstants.FIRST_PAGE); + } + + if (pagingInfo.getPageSize() > 0) // Ok we have a page size. + { + criteria.setFirstResult((pagingInfo.getCurrentPageNo() - CommonConstants.FIRST_PAGE) * pagingInfo.getPageSize()); + criteria.setMaxResults(pagingInfo.getPageSize()); + } + } + + //Add orderBy to ensure consistent results + criteria.addOrder(Order.asc("eventDate")); + + @SuppressWarnings("unchecked") + List jobEvents = criteria.list(); + + // Just in case test for null.... + if (jobEvents == null) + { + jobEvents = new ArrayList(); + } + + return jobEvents; + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Events since "+DateUtils.dateToString(changesSince, DateUtils.DISP_DATE_TIME_SEC)+" for fingerprint = '"+ fingerprint + ", zoneID = " + zoneID + ", contextID = "+ contextID + ", adapterType = '" + adapterType + "' and pagingInfo = "+pagingInfo+".", e); + } + } + + /* + * + */ + public List retrieveJobEvents(BasicTransaction tx, + Date eventsBefore, + String serviceName, + AdapterType adapterType, + JobEventType eventType, + boolean includeConsumeRequested) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + if (StringUtils.isEmpty(serviceName)) + { + throw new IllegalArgumentException("serviceName is null or empty."); + } + if (eventsBefore == null) + { + throw new IllegalArgumentException("eventsBefore is null."); + } + if (eventType == null) + { + throw new IllegalArgumentException("eventType is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(SIF3JobEvent.class); + + criteria = criteria.add(Restrictions.le("eventDate", eventsBefore)); + criteria = criteria.add(Restrictions.eq("adapterType", adapterType.name())); + criteria = criteria.add(Restrictions.eq("serviceName", serviceName)); + criteria = criteria.add(Restrictions.eq("eventType", eventType.name())); + + if (!includeConsumeRequested) // only get provider initiated events + { + criteria = criteria.add(Restrictions.eq("consumerRequested", false)); + } + + criteria = criteria.add(Restrictions.eq("published", false)); + + + //Add orderBy to ensure consistent results + criteria.addOrder(Order.asc("zoneID")).addOrder(Order.asc("contextID")).addOrder(Order.asc("fingerprint")).addOrder(Order.asc("internalID")); + + @SuppressWarnings("unchecked") + List jobEvents = criteria.list(); + + // Just in case test for null.... + if (jobEvents == null) + { + jobEvents = new ArrayList(); + } + + return jobEvents; + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Events for events before "+ + DateUtils.dateToString(eventsBefore, DateUtils.DISP_DATE_TIME_SEC)+ + " for adapterType = '" + adapterType + + "', serviceName = '" + serviceName + + "', eventType = '" + eventType + "'.", e); + } + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobTemplateDAO.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobTemplateDAO.java new file mode 100644 index 00000000..1cd40374 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/JobTemplateDAO.java @@ -0,0 +1,128 @@ +/* + * JobTemplateDAO.java + * Created: 21/12/2017 + * + * Copyright 2017 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package sif3.common.persist.dao; + +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.HibernateException; +import org.hibernate.criterion.Restrictions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.exception.PersistenceException; +import sif3.common.persist.common.BasicTransaction; +import sif3.common.persist.model.JobTemplate; + +/** + * Implements some low level DB operations relating to the job templates. + * + * @author Joerg Huber + * + */ +public class JobTemplateDAO extends BaseDAO +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * This method attempts to retrieve the job template by the primary key. If there is no row in the SIF3_JOB_TEMPLATE for the + * given templateID then null is returned. + * + * @param tx The current transaction. Cannot be null. + * @param jobTemplateID template ID for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * + * @return See desc. + * + * @throws IllegalArgumentException tx is null. + * @throws PersistenceException Could not access underlying data store. + */ + public JobTemplate getJobTemplate(BasicTransaction tx, long jobTemplateID) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + try + { + return (JobTemplate)tx.getSession().get(JobTemplate.class, jobTemplateID); + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Template for jobTemplateID = '"+ jobTemplateID + "'.", e); + } + } + + /** + * This method attempts to retrieve the job template by its URL Name and adapter type. If there is no row in the SIF3_JOB_TEMPLATE for the + * given parameters then null is returned. + * + * @param tx The current transaction. Cannot be null. + * @param jobURLName URL Name for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * @param adapterType Adapter Type for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * + * @return See desc. + * + * @throws IllegalArgumentException tx is null and/or jobURLName is null or empty and/or adapterType is null + * @throws PersistenceException Could not access underlying data store. + */ + public JobTemplate getJobTemplateForAdapter(BasicTransaction tx, String jobURLName, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + if (tx == null) + { + throw new IllegalArgumentException("Current transaction is null."); + } + + if (StringUtils.isEmpty(jobURLName)) + { + throw new IllegalArgumentException("jobURLName empty or null."); + } + + if (adapterType == null) + { + throw new IllegalArgumentException("adapterType is null."); + } + + try + { + Criteria criteria = tx.getSession().createCriteria(JobTemplate.class) + .add(Restrictions.eq("urlName", jobURLName)) + .add(Restrictions.eq("adapterType", adapterType.name())); + + @SuppressWarnings("unchecked") + List jobTemplates = criteria.list(); + + // There can only be a maximum of one + if (jobTemplates.isEmpty()) // no job templates for given name and adapter type + { + logger.debug("No Job Template for urlName = '"+ jobURLName + "' and adapterType = '" + adapterType + "' exists."); + return null; + } + else // already exists + { + return jobTemplates.get(0); + } + } + catch (HibernateException e) + { + throw new PersistenceException("Unable to retrieve Job Template for urlName = '"+ jobURLName + " and adapterType = '" + adapterType + "'.", e); + } + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/SIF3SubscriptionDAO.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/SIF3SubscriptionDAO.java index 7c307261..41a3134d 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/SIF3SubscriptionDAO.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/dao/SIF3SubscriptionDAO.java @@ -279,7 +279,7 @@ public SIF3Subscription saveSubscription(BasicTransaction tx, SIF3Subscription s } if (StringUtils.isEmpty(subscription.getServiceType())) { - throw new IllegalArgumentException("subscription.serviceType property is null or empty. It must be set to OBJECT, UTILITY or FUNCTION."); + throw new IllegalArgumentException("subscription.serviceType property is null or empty. It must be set to OBJECT, UTILITY or FUNCTIONAL."); } // If context is not given we set it to default context diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobBase.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobBase.java new file mode 100644 index 00000000..5a041fe6 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobBase.java @@ -0,0 +1,149 @@ +/* + * JobBase.java + * Created: 6 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.persist.model; + +import java.io.Serializable; + +/** + * @author Joerg Huber + * + */ +public class JobBase implements Serializable +{ + private static final long serialVersionUID = 8949924746543581132L; + + private long internalID; // sequence: auto generated + private String jobID = null; // Job UUID or RefID + private String serviceName = null; + private String environmentID = null; // Environment UUID or RefID + private String adapterType = null; //CONSUMER, PROVIDER, ENVIRONMENT_PROVIDER + private String fingerprint = null; + private String zoneID = null; + private String contextID = null; + private String jobXML = null; + + public JobBase() + { + super(); + } + + public JobBase(long internalID) + { + super(); + setInternalID(internalID); + } + + public long getInternalID() + { + return internalID; + } + + public void setInternalID(long internalID) + { + this.internalID = internalID; + } + + public String getJobID() + { + return jobID; + } + + public void setJobID(String jobID) + { + this.jobID = jobID; + } + + public String getServiceName() + { + return serviceName; + } + + public void setServiceName(String serviceName) + { + this.serviceName = serviceName; + } + + public String getEnvironmentID() + { + return environmentID; + } + + public void setEnvironmentID(String environmentID) + { + this.environmentID = environmentID; + } + + public String getAdapterType() + { + return adapterType; + } + + public void setAdapterType(String adapterType) + { + this.adapterType = adapterType; + } + + public String getFingerprint() + { + return fingerprint; + } + + public void setFingerprint(String fingerprint) + { + this.fingerprint = fingerprint; + } + + public String getZoneID() + { + return zoneID; + } + + public void setZoneID(String zoneID) + { + this.zoneID = zoneID; + } + + public String getContextID() + { + return contextID; + } + + public void setContextID(String contextID) + { + this.contextID = contextID; + } + + public String getJobXML() + { + return jobXML; + } + + public void setJobXML(String jobXML) + { + this.jobXML = jobXML; + } + + @Override + public String toString() + { + return "JobBase [internalID=" + internalID + ", jobID=" + jobID + ", serviceName=" + + serviceName + ", environmentID=" + environmentID + ", adapterType=" + adapterType + + ", fingerprint=" + fingerprint + ", zoneID=" + zoneID + ", contextID=" + contextID + + ", jobXML=" + jobXML + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobTemplate.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobTemplate.java new file mode 100644 index 00000000..829b976d --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/JobTemplate.java @@ -0,0 +1,83 @@ +/* + * JobTemplate.java + * Created: 21/12/2017 + * + * Copyright 2017 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package sif3.common.persist.model; + +import java.io.Serializable; + +/** + * POJO to encapsulate Job Template Names. + * + * @author Joerg Huber + * + */ +public class JobTemplate implements Serializable +{ + private static final long serialVersionUID = 159185591190684440L; + + private long templateID; + private String urlName = null; + private String adapterType; //CONSUMER, PROVIDER, ENVIRONMENT_PROVIDER + private String templateFileName = null; // Must be full name including file extension. + + public long getTemplateID() + { + return templateID; + } + + public void setTemplateID(long templateID) + { + this.templateID = templateID; + } + + public String getUrlName() + { + return urlName; + } + + public void setUrlName(String urlName) + { + this.urlName = urlName; + } + + public String getAdapterType() + { + return adapterType; + } + + public void setAdapterType(String adapterType) + { + this.adapterType = adapterType; + } + + public String getTemplateFileName() + { + return templateFileName; + } + + public void setTemplateFileName(String templateFileName) + { + this.templateFileName = templateFileName; + } + + @Override + public String toString() + { + return "JobTemplate [templateID=" + templateID + ", urlName=" + urlName + ", adapterType=" + + adapterType + ", templateFileName=" + templateFileName + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Infra.hbm.xml b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Infra.hbm.xml index 6861a3a5..bcfc2c8d 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Infra.hbm.xml +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Infra.hbm.xml @@ -83,6 +83,18 @@ + + + + + + + + + + + + @@ -129,5 +141,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Job.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Job.java new file mode 100644 index 00000000..f8ff0b06 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Job.java @@ -0,0 +1,103 @@ +/* + * SIF3Job.java + * Created: 6 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.persist.model; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author Joerg Huber + * + */ +public class SIF3Job extends JobBase implements Serializable +{ + private static final long serialVersionUID = 6667790445851364424L; + + private String currentState = null; + private Date created = null; + private String timeoutPeriod = null; + private Date lastModified = null; + private Date expireDatetime = null; + + public SIF3Job() + { + super(); + } + + public SIF3Job(long internalID) + { + super(internalID); + } + + public Date getCreated() + { + return created; + } + + public void setCreated(Date created) + { + this.created = created; + } + + public Date getLastModified() + { + return lastModified; + } + + public void setLastModified(Date lastModified) + { + this.lastModified = lastModified; + } + public String getCurrentState() + { + return currentState; + } + + public void setCurrentState(String currentState) + { + this.currentState = currentState; + } + + public String getTimeoutPeriod() + { + return timeoutPeriod; + } + + public void setTimeoutPeriod(String timeoutPeriod) + { + this.timeoutPeriod = timeoutPeriod; + } + + public Date getExpireDatetime() + { + return expireDatetime; + } + + public void setExpireDatetime(Date expireDatetime) + { + this.expireDatetime = expireDatetime; + } + + @Override + public String toString() + { + return "SIF3Job [currentState=" + currentState + ", created=" + created + ", timeoutPeriod=" + + timeoutPeriod + ", lastModified=" + lastModified + ", expireDatetime=" + + expireDatetime + ", toString()=" + super.toString() + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3JobEvent.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3JobEvent.java new file mode 100644 index 00000000..35358925 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3JobEvent.java @@ -0,0 +1,161 @@ +/* + * SIF3JobEvent.java + * Created: 6 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.persist.model; + +import java.io.Serializable; +import java.util.Date; + +import sif3.common.header.HeaderValues.EventAction; + +/** + * @author Joerg Huber + * + */ +public class SIF3JobEvent extends JobBase implements Serializable +{ + private static final long serialVersionUID = -7652413318428804910L; + + public static enum JobEventType {U, C, D}; + + private Date eventDate = null; // date and time when inserted into table + private String eventType = null; // U=Update, C=Create, D=Delete + private boolean fullUpdate = Boolean.TRUE; // TRUE = Update event is full update; FALSE = Update is partial + private boolean toFingerPrintOnly = Boolean.TRUE; // TRUE = Include fingerprint in event; FALSE = May not include fingerprint + private boolean consumerRequested = Boolean.TRUE; // TRUE = consumer requested an operation on the job + private boolean published = Boolean.FALSE; // TRUE = Event is already published; FALSE = Event isn't published, yet. + private Date publishedDate = null; // Date when event has been sent/published to event end-point. + + public SIF3JobEvent() + { + super(); + } + + /** + * Creates a Job Event object for the given event type. It will set the fingerprint only and consumerRequested + * flags as given. + * + * @param job Create an event with the data of the job. + * @param eventType The event type for this job event. + * @param fingerprintOnly TRUE then job event will be sent to fingerprint only (not yet used) + * @param consumerRequested Event was due to a consumer requested action on the job object. + */ + public SIF3JobEvent(SIF3Job job, EventAction eventType, boolean fingerprintOnly, boolean consumerRequested) + { + if (job != null) + { + setJobID(job.getJobID()); + setServiceName(job.getServiceName()); + setAdapterType(job.getAdapterType()); + setZoneID(job.getZoneID()); + setContextID(job.getContextID()); + setEnvironmentID(job.getEnvironmentID()); + setJobXML(job.getJobXML()); + setFingerprint(job.getFingerprint()); + setToFingerPrintOnly(fingerprintOnly); + setConsumerRequested(consumerRequested); + setEventDate(new Date()); + setEventType(eventType.name().substring(0, 1)); + setFullUpdate(true); + setPublished(false); + } + } + + public SIF3JobEvent(long internalID) + { + super(internalID); + } + + public Date getEventDate() + { + return eventDate; + } + + public void setEventDate(Date eventDate) + { + this.eventDate = eventDate; + } + + public String getEventType() + { + return eventType; + } + + public void setEventType(String eventType) + { + this.eventType = eventType; + } + + public boolean isFullUpdate() + { + return fullUpdate; + } + + public void setFullUpdate(boolean fullUpdate) + { + this.fullUpdate = fullUpdate; + } + + public boolean isToFingerPrintOnly() + { + return toFingerPrintOnly; + } + + public void setToFingerPrintOnly(boolean toFingerPrintOnly) + { + this.toFingerPrintOnly = toFingerPrintOnly; + } + + public boolean isConsumerRequested() + { + return consumerRequested; + } + + public void setConsumerRequested(boolean consumerRequested) + { + this.consumerRequested = consumerRequested; + } + + public boolean isPublished() + { + return published; + } + + public void setPublished(boolean published) + { + this.published = published; + } + + public Date getPublishedDate() + { + return publishedDate; + } + + public void setPublishedDate(Date publishedDate) + { + this.publishedDate = publishedDate; + } + + @Override + public String toString() + { + return "SIF3JobEvent [eventDate=" + eventDate + ", eventType=" + eventType + ", fullUpdate=" + + fullUpdate + ", toFingerPrintOnly=" + toFingerPrintOnly + ", consumerRequested=" + + consumerRequested + ", published=" + published + ", publishedDate=" + + publishedDate + ", toString()=" + super.toString() + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Session.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Session.java index 16077aa5..72dc1b08 100644 --- a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Session.java +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/model/SIF3Session.java @@ -27,12 +27,12 @@ import sif3.common.CommonConstants; import sif3.common.CommonConstants.AuthenticationType; import sif3.common.header.HeaderValues.ServiceType; +import sif3.common.model.ACL.AccessRight; +import sif3.common.model.ACL.AccessType; import sif3.common.model.EnvironmentKey; import sif3.common.model.SIFContext; import sif3.common.model.SIFZone; import sif3.common.model.ServiceInfo; -import sif3.common.model.ServiceRights.AccessRight; -import sif3.common.model.ServiceRights.AccessType; /** * POJO to encapsulate SIF3 Session Information and configuration. @@ -185,7 +185,6 @@ public void setQueueStrategy(String queueStrategy) { this.queueStrategy = StringUtils.isEmpty(queueStrategy) ? CommonConstants.QueueStrategy.ADAPTER_LEVEL.name() : queueStrategy; } - public Date getCreated() { @@ -268,6 +267,7 @@ public String getEnvironmentName() * @param right The access right (QUERY, UPDATE etc) that shall be checked for. * @param accessType The access level (SUPPORTED, APPROVED, etc) that must be met for the given service and right. * @param serviceName Service for which the access rights shall be checked. + * @param serviceType The type of the service. Eg. OBJECT, SERVICEPATH, FUNCTIONAL ... * @param zone The Zone for which the service is valid and for which the access rights shall be checked. This can be * null and would indicate the default zone. * @param context The context for which the service is valid and for which the access rights shall be checked. This @@ -275,13 +275,20 @@ public String getEnvironmentName() * * @return See desc */ - public boolean hasAccess(AccessRight right, AccessType accessType, String serviceName, SIFZone zone, SIFContext context) + public boolean hasAccess(AccessRight right, AccessType accessType, String serviceName, ServiceType serviceType, SIFZone zone, SIFContext context) { boolean accessApproved = false; for (ServiceInfo serviceInfo : getServices()) { if (serviceInfo.getServiceName().equals(serviceName)) //service name matches { + // Check if service type matches + boolean serviceTypeMatches = true; + if (serviceType != null) + { + serviceTypeMatches = (serviceInfo.getServiceType() == serviceType); + } + //Check if Zone matches boolean zoneMatches = (zone == null) ? serviceInfo.getZone().getIsDefault() : zone.getId().equals(serviceInfo.getZone().getId()); @@ -289,9 +296,9 @@ public boolean hasAccess(AccessRight right, AccessType accessType, String servic boolean contextMatches = (context == null) ? serviceInfo.getContext().getIsDefault() : context.getId().equals(serviceInfo.getContext().getId()); // Check if access right is the correct level - if (zoneMatches && contextMatches) + if (zoneMatches && contextMatches && serviceTypeMatches) { - accessApproved = serviceInfo.getRights().hasRight(right, accessType); + accessApproved = serviceInfo.getRights().hasRight(right, accessType); } } } diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/service/JobService.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/service/JobService.java new file mode 100644 index 00000000..90ab029a --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/persist/service/JobService.java @@ -0,0 +1,640 @@ +/* + * JobService.java + * Created: 21/12/2017 + * + * Copyright 2017 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package sif3.common.persist.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import sif3.common.CommonConstants.AdapterType; +import sif3.common.exception.PersistenceException; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.PagingInfo; +import sif3.common.persist.common.BasicTransaction; +import sif3.common.persist.dao.BaseDAO; +import sif3.common.persist.dao.JobDAO; +import sif3.common.persist.dao.JobTemplateDAO; +import sif3.common.persist.model.JobTemplate; +import sif3.common.persist.model.SIF3Job; +import sif3.common.persist.model.SIF3JobEvent; +import sif3.common.persist.model.SIF3JobEvent.JobEventType; + +/** + * @author Joerg Huber + * + */ +public class JobService extends DBService +{ + private JobTemplateDAO jobTemplateDAO = new JobTemplateDAO(); + private JobDAO jobDAO = new JobDAO(); + + @Override + public BaseDAO getDAO() + { + return jobTemplateDAO; + } + + /** + * This method attempts to retrieve the job template by the primary key. If there is no row in the SIF3_JOB_TEMPLATE for the + * given templateID then null is returned. + * + * @param jobTemplateID template ID for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * + * @return See desc. + * + * @throws PersistenceException Could not access underlying data store. + */ + public JobTemplate getJobTemplate(long jobTemplateID) throws IllegalArgumentException, PersistenceException + { + JobTemplate row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobTemplateDAO.getJobTemplate(tx, jobTemplateID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + /** + * This method attempts to retrieve the job template by its URL Name and adapter type. If there is no row in the SIF3_JOB_TEMPLATE for the + * given parameters then null is returned. + * + * @param jobURLName URL Name for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * @param adapterType Adapter Type for which the job template information from the SIF3_JOB_TEMPLATE table shall be returned. + * + * @return See desc. + * + * @throws IllegalArgumentException jobURLName is null or empty and/or adapterType is null + * @throws PersistenceException Could not access underlying data store. + */ + public JobTemplate getJobTemplateForAdapter(String jobURLName, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + JobTemplate row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobTemplateDAO.getJobTemplateForAdapter(tx, jobURLName, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + public SIF3Job getJob(long internalID) throws IllegalArgumentException, PersistenceException + { + SIF3Job row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.getJob(tx, internalID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + public SIF3Job getJobByUUID(String uuid, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + SIF3Job row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.getJobByUUID(tx, uuid, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + public List retrieveJobs(String serviceName, String fingerprint, String zoneID, String contextID, PagingInfo pagingInfo, AdapterType adapterType) throws PersistenceException + { + List jobs = null; + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobs = jobDAO.retrieveJobs(tx, serviceName, fingerprint, zoneID, contextID, pagingInfo, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + + return jobs; + } + + /** + * This method will add a Job to the SIF3_JOB table. Please refer to the appropriate method in the JobDAO for details on + * pre- and post-conditions. + * + * @param job The job to be added to the SIF3_JOB table. + * + * @return The newly created job. This has now the internalID populated. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3Job createJob(SIF3Job job) throws IllegalArgumentException, PersistenceException + { + SIF3Job row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.createJob(tx, job); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + /** + * Updates a job with the given values. Note that the internalID must be set and have the value of an existing object. The job object is + * updated with the exact values given. Please refer to the appropriate method in the JobDAO for details on pre- and post-conditions.
+ * + * @param job The job to be updated to the SIF3_JOB table. + * + * @return The updated job. This has a new lastModified date which is now the current date. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3Job updateJob(SIF3Job job) throws IllegalArgumentException, PersistenceException + { + SIF3Job row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.updateJob(tx, job); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to update Job\n" +job+ "\nReported Error: "+ex.getMessage(), false, false); + } + return row; + } + + public void removeJob(long internalID) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeJob(tx, internalID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete Job with intenalID = "+internalID+". Job may not exists. Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the expire date is older than the current date. if no expire date is + * set then these jobs won't be removed. + * + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeExpiredJobs(AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeExpiredJobs(tx, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete JOBs for entries. Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the job has not been updated since a given date. In other words the + * last update date & time is before the noUpdateSince date. This can be considered a stale job. They will be removed only if + * there is no expire date & time. If an expire date & time is available then the job won't be removed, regardless if it hasn't + * been updated since the given noUpdateSince. To remove expired jobs, use the removeExpiredJobs() method. + * + * @param noUpdateSince The date for jobs to be removed that have not been updated since that date and do not have an expire date. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the noUpdateSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeJobsWithoutChangeSince(Date noUpdateSince, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeJobsWithoutChangeSince(tx, noUpdateSince, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete JOBs for entries. Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the job has been created before the noUpdateSince date but has + * no last update date and no expire date. In other words the creation date & time is before the noUpdateSince date. + * This can be considered a stale job. They will be removed only if there is no expire and no last update date & time. + * If an expire or last update date & time is available then the job won't be removed, regardless if it has been created before + * the noUpdateSince. To remove expired jobs, use the removeExpiredJobs() method. To remove jobs that have last been updated\ + * before a given date use the removeJobsWithoutChangeSince() method. + * + * @param noUpdateSince The date for jobs to be removed that have not been created before that date and do not have an expire date + * or last update date. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the noUpdateSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeCreatedJobsWithoutChangeSince(Date noUpdateSince, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeCreatedJobsWithoutChangeSince(tx, noUpdateSince, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete JOBs for entries. Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method removes all rows in the SIF3_JOB table where the job has:
+ * - Expired
+ * - Has a last updated date older than noUpdateSince
+ * - Has been created but not been update since noUpdateSince

+ * + * It is a convenience method that combines the removeExpiredJobs(), removeJobsWithoutChangeSince() and + * removeCreatedJobsWithoutChangeSince() method into one call. See details for all the mentioned methods. + * + * @param noUpdateSince The date for jobs to be removed that have not been created before that date and do not have an expire date + * or last update date. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the noUpdateSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeExpiredAndStaleJobs(Date noUpdateSince, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeExpiredJobs(tx, adapterType); + jobDAO.removeJobsWithoutChangeSince(tx, noUpdateSince, adapterType); + jobDAO.removeCreatedJobsWithoutChangeSince(tx, noUpdateSince, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete JOBs for entries. Reported Error: "+ex.getMessage(), false, false); + } + + } + + //-------------------------------// + //-- JOB Event related methods --// + //------------------------------// + + public SIF3JobEvent getJobEvent(long internalID) throws IllegalArgumentException, PersistenceException + { + SIF3JobEvent row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.getJobEvent(tx, internalID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + public List getJobEventsByUUID(String uuid, AdapterType adapterType, EventAction eventType, Boolean notYetPublished) throws IllegalArgumentException, PersistenceException + { + List rows = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + rows = jobDAO.getJobEventsByUUID(tx, uuid, adapterType, eventType, notYetPublished); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return rows; + } + + /** + * This method will add a Job Event to the SIF3_JOB_EVENT table. The event is not sent as part of this event!
+ * The event must have a minimum set of data, otherwise an IllegalArgumentExceptionis thrown. Please refer to the + * appropriate method in the JobDAO for details on these pre- and post-conditions. + * + * @param jobEvent The event to be added to the SIF3_JOB_EVENT table. + * + * @return The newly created job event. This has now the internalID and the event date and published populated. The published will be set to FALSE + * and the event date will be set to current datetime. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3JobEvent createJobEvent(SIF3JobEvent jobEvent) throws IllegalArgumentException, PersistenceException + { + SIF3JobEvent row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.createJobEvent(tx, jobEvent); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + /** + * This method creates an event based on the given Job and teh other parameters. If the job.xml is empty then an event cannot really + * be created as crucial data is missing. The only exception is the delete event which only requires the job.id. There are a number of + * other pre-conditions to be met. They are listed in the createJobEvent(BasicTransaction, SIF3JobEvent) method. + * + * @param job The job based on which an event shall be created. If null then no event is created and null is returned. + * @param eventType The event type (CREATE, UPDATE, DELETE). + * @param fingerprintOnly Indicating if the event is for the 'fingerprint' consumer only. + * @param consumerRequested Indicating if the event is caused by consumer requested operation on job. + * + * @return The newly created job event. This has now the internalID and the event date and published populated. + * The published will be set to FALSE and the event date will be set to current datetime. + * + * @throws IllegalArgumentException If the tx is null or any of the rules listed above are not met. + * @throws PersistenceException Could not access underlying data store. + */ + public SIF3JobEvent createJobEvent(SIF3Job job, EventAction eventType, boolean fingerprintOnly, boolean consumerRequested) throws IllegalArgumentException, PersistenceException + { + SIF3JobEvent row = null; + BasicTransaction tx = null; + + try + { + tx = startTransaction(); + row = jobDAO.createJobEvent(tx, job, eventType, fingerprintOnly, consumerRequested); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + return row; + } + + + public void markJobEventAsPublished(long internalID) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.markJobEventAsPublished(tx, internalID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + } + + public void removeJobEvent(long internalID) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeJobEvent(tx, internalID); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete Job Event with intenalID = "+internalID+". Job Event may not exists. Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method removes all rows in the SIF3_JOB_EVENT table that are older than the given date (inclusive). + * + * @param olderThanDate The date to use. All entries older than this date will removed. + * @param adapterType The adapter type for which the events shall be removed. + * + * @throws IllegalArgumentException If the olderThanDate or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public void removeJobEvents(Date olderThanDate, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobDAO.removeJobEvents(tx, olderThanDate, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, "Failed to delete JOB Event log for entries older than "+olderThanDate.toString()+". Reported Error: "+ex.getMessage(), false, false); + } + } + + /** + * This method returns all Job Events that have been created on or after the 'changesSince' date. The number of rows to return + * is indicated by the 'pagingInfo' parameter. + * + * @param changesSince Must not be null. + * @param serviceName The service name for which the events shall be returned. Can be null. + * @param fingerprint The fingerprint for which the events shall be returned. Can be null. + * @param zoneID The zone for which the events shall be returned. Can be null. + * @param contextID The context for which the events shall be returned. Can be null. + * @param pagingInfo Paging Info. Indicating which page and how many rows to return. Can be null => return all. First page = 1. + * @param adapterType Must not be null. + * + * @return A list of job events as described. Is never null. + * + * @throws IllegalArgumentException If the changesSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public List retrieveJobEventsSince(Date changesSince, String serviceName, String fingerprint, String zoneID, String contextID, PagingInfo pagingInfo, AdapterType adapterType) throws IllegalArgumentException, PersistenceException + { + List jobEvents = null; + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobEvents = jobDAO.retrieveJobEventsSince(tx, changesSince, serviceName, fingerprint, zoneID, contextID, pagingInfo, adapterType); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + + return jobEvents; + } + + /** + * This method returns all events in the SIF3_JOB_EVENT table that match the given parameters. If the 'includeConsumeRequested' + * is set to TRUE then all events initiated by the provider and consumer are returned. If 'includeConsumeRequested' is set + * to false only events caused by changes from the provider are returned. This behaviour is useful as in most cases the + * consumer, who requested an operation on a job, already has the response of the request and therefore has no need to receive + * an event about that action. The consumer only wants to know about events it has not initiated, meaning provider caused events. + * + * @param eventsBefore Return events before this date. + * @param serviceName Events for the service to be returned. + * @param adapterType The adapter type which is ENVIRONMENT_PROVIDER for a direct environment or PROVIDER for a brokered + * environment. + * @param eventType The event type (Create, Update, Change) to be retrieved. + * @param includeConsumeRequested Include consumer caused events. + * + * @return A list of events that match the criteria given by the parameters. The returned list is ordered by ZoneId, ContextId, + * fingerprint & eventID (in this order). + * + * @throws IllegalArgumentException Any of the input parameters is null. + * @throws PersistenceException Failed to retrieve events due to a DB issue. + */ + public List retrieveJobEvents(Date eventsBefore, + String serviceName, + AdapterType adapterType, + JobEventType eventType, + boolean includeConsumeRequested) throws IllegalArgumentException, PersistenceException + { + List jobEvents = null; + BasicTransaction tx = null; + try + { + tx = startTransaction(); + jobEvents = jobDAO.retrieveJobEvents(tx, eventsBefore, serviceName, adapterType, eventType, includeConsumeRequested); + tx.commit(); + } + catch (Exception ex) + { + rollback(tx); + exceptionMapper(ex, ex.getMessage(), false, false); + } + + return jobEvents; + } + + /** + * This is a convenience method that is used to get a full list of events in the order of Create, Update and Delete before the + * current date and time. The final returned list has all Create events before the Update Events and then the Delete events. + * Within each event type the order is by ZoneId, ContextId, fingerprint & eventID. This list is intended to be used by the + * event publisher as it has the events already lined up as they should be published. + * + * @param serviceName Events for the service to be returned. + * @param adapterType The adapter type which is ENVIRONMENT_PROVIDER for a direct environment or PROVIDER for a brokered + * environment. + * @param includeConsumeRequested Include consumer caused events. + * + * @return A list of events that match the criteria given by the parameters. Ordering of the returned list is outlined in the + * description. + * + * @throws IllegalArgumentException Any of the input parameters is null. + * @throws PersistenceException Failed to retrieve events due to a DB issue. + */ + public List retrieveJobEvents(String serviceName, AdapterType adapterType, boolean includeConsumeRequested) throws IllegalArgumentException, PersistenceException + { + List finalJobEvents = new ArrayList(); + Date now = new Date(); + + // Get all Create Events + finalJobEvents.addAll(retrieveJobEvents(now, serviceName, adapterType, JobEventType.C, includeConsumeRequested)); + finalJobEvents.addAll(retrieveJobEvents(now, serviceName, adapterType, JobEventType.U, includeConsumeRequested)); + finalJobEvents.addAll(retrieveJobEvents(now, serviceName, adapterType, JobEventType.D, includeConsumeRequested)); + + return finalJobEvents; + } + +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseData.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseData.java new file mode 100644 index 00000000..b793cbbb --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseData.java @@ -0,0 +1,77 @@ +/* + * PhaseData.java + * Created: 19 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.ws.job; + +import java.io.Serializable; + +import javax.ws.rs.core.MediaType; + +/** + * This class encapsulates typical data that is passed from/to a phase of a functional service. Almost all phase operations deal + * with some data, either in the request, the response or both. The framework is data model agnostic and therefore doesn't + * know what payloads are passed to or from a phase. The data is passed around in its raw String representation with the applicable + * mime type, so that the recipient of the data knows how what it deals with. + * + * @author Joerg Huber + * + */ +public class PhaseData implements Serializable +{ + private static final long serialVersionUID = -4749828404357889220L; + + private String data = null; + private MediaType mimeType = null; + + public PhaseData() + { + this(null, null); + } + + public PhaseData(String data, MediaType mimeType) + { + super(); + this.data = data; + this.mimeType = mimeType; + } + + public String getData() + { + return data; + } + + public void setData(String data) + { + this.data = data; + } + + public MediaType getMimeType() + { + return mimeType; + } + + public void setMimeType(MediaType mimeType) + { + this.mimeType = mimeType; + } + + @Override + public String toString() + { + return "PhaseData [data=" + data + ", mimeType=" + mimeType + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataRequest.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataRequest.java new file mode 100644 index 00000000..b9c56791 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataRequest.java @@ -0,0 +1,45 @@ +/* + * PhaseDataRequest.java + * Created: 19 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.ws.job; + +import javax.ws.rs.core.MediaType; + +/** + * @author Joerg Huber + * + */ +public class PhaseDataRequest extends PhaseData +{ + private static final long serialVersionUID = -1760425027860602060L; + + public PhaseDataRequest() + { + this(null, null); + } + + public PhaseDataRequest(String data, MediaType mimeType) + { + super(data, mimeType); + } + + @Override + public String toString() + { + return "PhaseDataRequest [toString()=" + super.toString() + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataResponse.java b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataResponse.java new file mode 100644 index 00000000..35f4eac6 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/main/java/sif3/common/ws/job/PhaseDataResponse.java @@ -0,0 +1,59 @@ +/* + * PhaseDataResponse.java + * Created: 19 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.common.ws.job; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +/** + * @author Joerg Huber + * + */ +public class PhaseDataResponse extends PhaseData +{ + private static final long serialVersionUID = 3148009552559307122L; + + public Status status = null; + + public PhaseDataResponse() + { + this(null, null, null); + } + + public PhaseDataResponse(String data, MediaType mimeType, Status status) + { + super(data, mimeType); + setStatus(status); + } + + public Status getStatus() + { + return status; + } + + public void setStatus(Status status) + { + this.status = status; + } + + @Override + public String toString() + { + return "PhaseDataResponse [status=" + status + ", toString()=" + super.toString() + "]"; + } +} diff --git a/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/model/TestPathInfo.java b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/model/TestPathInfo.java index 9564f76a..9145f83e 100644 --- a/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/model/TestPathInfo.java +++ b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/model/TestPathInfo.java @@ -25,14 +25,27 @@ */ public class TestPathInfo { + public static final String OBJ_SVC= "Object"; + public static final String SP_SVC= "SERVICEPath"; + public static final String FS_SVC= "FUNCTIONAL"; + public static void main(String[] argv) { - System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFAULT")); - System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFAULT?myParam=7")); - System.out.println(new URIPathInfo("/SchoolInfos.xml;zoneId=ABC;")); - System.out.println(new URIPathInfo("/SchoolInfos/1234;zoneId=ABC;")); - System.out.println(new URIPathInfo("/SchoolInfos;zoneId=ABC;test=2?myParam=aabss")); - System.out.println(new URIPathInfo("/SchoolInfos;zoneId=ABC;test=2?myParam=aabss¶m2=¶m3=56")); - System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonals/9876-0998AF/StudentDailyAttendances;zoneId=ABC;contextId=DEFAULT?myParam=7")); + // Object Services + System.out.println(new URIPathInfo("/SchoolInfos.xml;zoneId=ABC;", OBJ_SVC)); + System.out.println(new URIPathInfo("/SchoolInfos/1234;zoneId=ABC;", OBJ_SVC)); + System.out.println(new URIPathInfo("/SchoolInfos;zoneId=ABC;test=2?myParam=aabss", OBJ_SVC)); + System.out.println(new URIPathInfo("/SchoolInfos;zoneId=ABC;test=2?myParam=aabss¶m2=¶m3=56", OBJ_SVC)); + + //Service Paths + System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFAULT", SP_SVC)); + System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonal.xml;zoneId=ABC;contextId=DEFAULT?myParam=7", SP_SVC)); + System.out.println(new URIPathInfo("/SchoolInfos/1234-1245/StudentPersonals/9876-0998AF/StudentDailyAttendances;zoneId=ABC;contextId=DEFAULT?myParam=7", SP_SVC)); + + // Job Services + System.out.println(new URIPathInfo("/StudentRollover;contextId=DEFAULT?myParam=7", FS_SVC)); + System.out.println(new URIPathInfo("/StudentRollover/1234-7765-efee;contextId=DEFAULT", FS_SVC)); + System.out.println(new URIPathInfo("/StudentRollover/1234-7765-efee/Phase1?myParam=7", FS_SVC)); + } } diff --git a/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/persist/service/TestJobService.java b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/persist/service/TestJobService.java new file mode 100644 index 00000000..03af4ea6 --- /dev/null +++ b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/persist/service/TestJobService.java @@ -0,0 +1,251 @@ +/* + * TestAppEnvTemplateService.java + * Created: 21/12/2017 + * + * Copyright 2017 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.common.test.persist.service; + +import java.util.Date; +import java.util.List; + +import au.com.systemic.framework.utils.DateUtils; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.PagingInfo; +import sif3.common.persist.model.SIF3Job; +import sif3.common.persist.model.SIF3JobEvent; +import sif3.common.persist.service.JobService; +import sif3.common.utils.UUIDGenerator; + +/** + * @author Joerg Huber + * + */ +public class TestJobService extends ServiceBaseTest +{ + private static final String JOB_ID = "aaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee"; + + private JobService service = new JobService(); + + public void testGetJobTemplate(long templateID) throws Exception + { + System.out.println("Job Template Info: "+service.getJobTemplate(templateID)); + } + + public void testGetJobTemplateForURLNameAndAdpater(String jobURLName, AdapterType adapterType) throws Exception + { + System.out.println("Job Template Info: "+service.getJobTemplateForAdapter(jobURLName, adapterType)); + } + + //----------------------// + //-- JOB related Test --// + //----------------------// + public void testGetJob(long internalID) throws Exception + { + System.out.println("Job Info for internalID = "+internalID+":\n"+service.getJob(internalID)); + } + + public void testGetJobByUUID(String uuid, AdapterType adapterType) throws Exception + { + System.out.println("Job Info for uuid/adapterType = "+uuid+"/" + adapterType+":\n"+service.getJobByUUID(uuid, adapterType)); + } + + public void testCreateJob() throws Exception + { + SIF3Job job = new SIF3Job(); +// job.setJobID(JOB_ID); + job.setJobID(UUIDGenerator.getUUID()); + job.setServiceName("rolloverStudent"); + job.setEnvironmentID("545c31ac-85c7-4360-bd63-24d425be1532"); + job.setAdapterType(AdapterType.ENVIRONMENT_PROVIDER.name()); + job.setJobXML(""); + job.setFingerprint("3ae742dc-0c74-444f-b761-560a9fec5178"); + job.setTimeoutPeriod("P3D"); + + System.out.println("Newly Created Job:\n"+service.createJob(job)); + } + + public void testUpdateJob() throws Exception + { + SIF3Job job = service.getJob(36); + job.setZoneID("TestZone"); + job.setContextID("DEFAULT"); + job.setInternalID(36); + + System.out.println("Updated Job:\n"+service.updateJob(job)); + } + + public void testRemoveJob(long internalID) throws Exception + { + service.removeJob(internalID); + System.out.println("Job with internalID = "+internalID+" removed."); + } + + //----------------------------// + //-- JOB Event related Test --// + //---------------------------// + public void testGetJobEvent(long internalID) throws Exception + { + System.out.println("Job Event Info for internalID = "+internalID+":\n"+service.getJobEvent(internalID)); + } + + public void testGetJobEventsByUUID(String uuid, AdapterType adapterType, EventAction eventType, Boolean notYetPublished) throws Exception + { + System.out.println("Job Event List for uuid/adapterType/eventType/notYetPublised = "+uuid+"/" + adapterType+"/" + eventType+"/" + notYetPublished+":\n"+service.getJobEventsByUUID(uuid, adapterType, eventType, notYetPublished)); + } + + public void testCreateJobEvent() throws Exception + { + SIF3JobEvent jobEvent = new SIF3JobEvent(); + jobEvent.setJobID(JOB_ID); +// jobEvent.setJobID(UUIDGenerator.getUUID()); + jobEvent.setEnvironmentID("545c31ac-85c7-4360-bd63-24d425be1532"); + jobEvent.setAdapterType(AdapterType.ENVIRONMENT_PROVIDER.name()); + jobEvent.setEventType("U"); + jobEvent.setJobXML(""); + jobEvent.setToFingerPrintOnly(Boolean.TRUE); + jobEvent.setFingerprint("3ae742dc-0c74-444f-b761-560a9fec5178"); + jobEvent.setServiceName("RollowverStudents"); + + // The following should be overriden with defaults! + jobEvent.setPublished(Boolean.TRUE); // Should be overriden to false! + jobEvent.setPublishedDate(DateUtils.stringToDate("01/02/2018", DateUtils.DISP_LONG_YEAR)); + + System.out.println("Newly Created Event:\n"+service.createJobEvent(jobEvent)); + } + + public void testMarkJobEventAsPublished(long internalID) throws Exception + { + service.markJobEventAsPublished(internalID); + System.out.println("Job Event with internalID = "+internalID+" marked as updated."); + } + + public void testRemoveJobEvent(long internalID) throws Exception + { + service.removeJobEvent(internalID); + System.out.println("Job Event with internalID = "+internalID+" removed."); + } + + public void testRemoveOlderThanJobEvents() throws Exception + { + String dateStr = "07/06/2018 11:00:00"; + Date olderThan = DateUtils.stringToDate(dateStr, DateUtils.DISP_DATE_TIME_SEC); + service.removeJobEvents(olderThan, AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Job Events older than "+dateStr+" removed."); + } + + public void testRetrieveJobEventsSince() throws Exception + { + String dateStr = "08/06/2018 00:00:00"; + Date changesSince = DateUtils.stringToDate(dateStr, DateUtils.DISP_DATE_TIME_SEC); + List jobEvents = service.retrieveJobEventsSince(changesSince, "RolloverStudents", "3ae742dc-0c74-444f-b761-560a9fec5178","auSchoolTestingZone", null, new PagingInfo(5, 2), AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Job Events: "+jobEvents); + } + + public void testRetrieveJobEvents() throws Exception + { + AdapterType adapterType = AdapterType.ENVIRONMENT_PROVIDER; + String serviceType = "RolloverStudents"; + List jobEvents = service.retrieveJobEvents(serviceType, adapterType, true); + System.out.println("Events for Service " +serviceType + " and AdapterType " + adapterType + ":"); + for (SIF3JobEvent event : jobEvents) + { + System.out.println("internalID=" + event.getInternalID() + + ", eventType=" + event.getEventType() + + ", zoneID=" + event.getZoneID() + + ", contextID=" + event.getContextID() + + ", fingerprint=" + event.getFingerprint() + + ", jobID=" + event.getJobID() + + ", eventDate=" + event.getEventDate() + + ", consumerRequested=" + event.isConsumerRequested()); + } + } + + + public void testRemoveExpiredJobs() throws Exception + { + service.removeExpiredJobs(AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Expired Jobs removed."); + } + + public void testRemoveJobsWithoutChangeSince() throws Exception + { + String dateStr = "07/06/2018 00:00:00"; + Date olderThan = DateUtils.stringToDate(dateStr, DateUtils.DISP_DATE_TIME_SEC); + service.removeJobsWithoutChangeSince(olderThan, AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Job with last updated older than "+dateStr+" removed."); + } + + public void testRemoveCreatedJobsWithoutChangeSince() throws Exception + { + String dateStr = "07/06/2018 00:00:00"; + Date olderThan = DateUtils.stringToDate(dateStr, DateUtils.DISP_DATE_TIME_SEC); + service.removeCreatedJobsWithoutChangeSince(olderThan, AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Job with creation date older than "+dateStr+" removed."); + } + + public void testRemoveExpiredAndStaleJobs() throws Exception + { + String dateStr = "07/06/2018 00:00:00"; + Date olderThan = DateUtils.stringToDate(dateStr, DateUtils.DISP_DATE_TIME_SEC); + service.removeExpiredAndStaleJobs(olderThan, AdapterType.ENVIRONMENT_PROVIDER); + System.out.println("Job expired or stale jobs older than "+dateStr+" removed."); + } + + + public static void main(String[] args) + { + System.out.println("================================== Start TestJobService ==============================="); + try + { + TestJobService tester = new TestJobService(); +// tester.testGetJobTemplate(1); +// tester.testGetJobTemplate(999); +// tester.testGetJobTemplateForURLNameAndAdpater("RolloverStudents", AdapterType.ENVIRONMENT_PROVIDER); +// tester.testGetJobTemplateForURLNameAndAdpater("TestNotExist", AdapterType.CONSUMER); +// tester.testGetJobTemplateForURLNameAndAdpater(null, AdapterType.CONSUMER); + +// tester.testGetJob(1); +// tester.testGetJobByUUID("123-456", AdapterType.CONSUMER); +// tester.testGetJobByUUID("123-456", AdapterType.ENVIRONMENT_PROVIDER); +// tester.testGetJobEvent(1); +// tester.testGetJobEventsByUUID(JOB_ID, AdapterType.ENVIRONMENT_PROVIDER, null, null); +// tester.testGetJobEventsByUUID(JOB_ID, AdapterType.ENVIRONMENT_PROVIDER, EventAction.UPDATE, null); +// tester.testGetJobEventsByUUID(JOB_ID, AdapterType.ENVIRONMENT_PROVIDER, EventAction.UPDATE, Boolean.FALSE); +// tester.testGetJobEventsByUUID(JOB_ID, AdapterType.ENVIRONMENT_PROVIDER, null, Boolean.FALSE); +// tester.testCreateJobEvent(); +// tester.testMarkJobEventAsPublished(2); +// tester.testRemoveJobEvent(3); +// tester.testCreateJob(); +// tester.testUpdateJob(); +// tester.testRemoveJob(7); +// tester.testRemoveOlderThanJobEvents(); +// tester.testRemoveExpiredJobs(); +// tester.testRemoveJobsWithoutChangeSince(); +// tester.testRemoveCreatedJobsWithoutChangeSince(); +// tester.testRemoveExpiredAndStaleJobs(); +// tester.testRetrieveJobEventsSince(); + tester.testRetrieveJobEvents(); + + tester.shutdown(); + } + catch (Exception ex) + { + ex.printStackTrace(); + } + System.out.println("================================== End TestJobService ==============================="); + } +} diff --git a/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/utils/TestAuthUtils.java b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/utils/TestAuthUtils.java index efa7376c..a7398868 100644 --- a/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/utils/TestAuthUtils.java +++ b/SIF3InfraREST/sif3Common/src/test/java/sif3/common/test/utils/TestAuthUtils.java @@ -90,8 +90,8 @@ private void testHMACSHA256Stress() private void testHMACSHA256Token() { // String username = "TestSIS"; - String username = "d394e915-ebb0-4c22-b4ec-8a0abed239df"; - String password = "password"; + String username = "27677240-0c2e-4e65-a47f-e3454e5234a1"; + String password = "bijObw7aBbTB"; // String username = "new"; // String password = "guest"; /// String now = "2013-06-22T23:52-07"; @@ -185,10 +185,10 @@ public static void main(String[] args) // tester.testGetBasicAuthToken(); // tester.testHMACSHA256(); //tester.testHMACSHA256Stress(); -// tester.testHMACSHA256Token(); + tester.testHMACSHA256Token(); // tester.testHMACSHA256TokenStress(); // tester.testExtractMethods(); - tester.testGetPartsFromAuthToken(); +// tester.testGetPartsFromAuthToken(); } catch (Exception ex) { diff --git a/SIF3InfraREST/sif3Common/src/test/java/systemic/framework/utils/test/TestDateUtils.java b/SIF3InfraREST/sif3Common/src/test/java/systemic/framework/utils/test/TestDateUtils.java index 033d9b7d..c43cbe9f 100644 --- a/SIF3InfraREST/sif3Common/src/test/java/systemic/framework/utils/test/TestDateUtils.java +++ b/SIF3InfraREST/sif3Common/src/test/java/systemic/framework/utils/test/TestDateUtils.java @@ -18,6 +18,10 @@ package systemic.framework.utils.test; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + import au.com.systemic.framework.utils.DateUtils; /** @@ -26,6 +30,9 @@ */ public class TestDateUtils { + private static TimeZone gmt = TimeZone.getTimeZone("GMT"); + private static TimeZone local = TimeZone.getDefault(); + private static void delay(long millisec) { try @@ -38,9 +45,25 @@ private static void delay(long millisec) } } + private static void timeZoneTest() + { + Date now = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + String localTime = sdf.format(now); + + sdf.setTimeZone(gmt); + String gmtTime = sdf.format(now); + + System.out.println("Local Time = "+localTime+"; GMT Time = "+gmtTime); + } + public static void main(String[] args) { - System.out.println("Current Date & Time in ISO 8601: " + DateUtils.nowAsISO8601()); + System.out.println("Local Time Zone: " + local); + System.out.println("GMT Time Zone: " + gmt); + + System.out.println("Current Date & Time in ISO 8601: " + DateUtils.nowAsISO8601()); System.out.println("Current Date & Time in ISO 8601 with Sec Fractions: " + DateUtils.nowAsISO8601withSecFraction()); delay(120); System.out.println("Current Date & Time in ISO 8601 with Sec Fractions: " + DateUtils.nowAsISO8601withSecFraction()); @@ -54,5 +77,7 @@ public static void main(String[] args) System.out.println("Current Date & Time in ISO 8601 with Sec Fractions: " + DateUtils.nowAsISO8601withSecFraction()); delay(200); System.out.println("Current Date & Time in ISO 8601 with Sec Fractions: " + DateUtils.nowAsISO8601withSecFraction()); + + timeZoneTest(); } } diff --git a/SIF3InfraREST/sif3InfraCommon/pom.xml b/SIF3InfraREST/sif3InfraCommon/pom.xml index e55fb083..706c60aa 100644 --- a/SIF3InfraREST/sif3InfraCommon/pom.xml +++ b/SIF3InfraREST/sif3InfraCommon/pom.xml @@ -7,7 +7,7 @@ sif3.framework sif3-framework - 0.12.0-beta + 0.13.0-beta diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseConsumerJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseConsumerJobManager.java new file mode 100644 index 00000000..4b52ff88 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseConsumerJobManager.java @@ -0,0 +1,33 @@ +/* + * BaseConsumerJobManager.java + * Created: 11 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import sif3.infra.common.env.types.EnvironmentInfo; + +/** + * @author Joerg Huber + * + */ +public abstract class BaseConsumerJobManager extends BaseJobManager implements sif3.infra.common.interfaces.ConsumerJobManager +{ + protected BaseConsumerJobManager(EnvironmentInfo environmentInfo) + { + super(environmentInfo); + } + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseEnvironmentManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseEnvironmentManager.java new file mode 100644 index 00000000..d556b7b0 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseEnvironmentManager.java @@ -0,0 +1,59 @@ +/* + * BaseEnvironmentManager.java + * Created: 9 Jan 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import sif3.common.CommonConstants.AdapterType; +import sif3.common.persist.model.JobTemplate; +import sif3.infra.common.env.ops.AdapterBaseEnvStoreOperations; +import sif3.infra.common.interfaces.JobManager; +import sif3.infra.common.model.JobType; + +/** + * This class has a few hand and common methods used by all Environment Manager classes. + * + * @author Joerg Huber + * + */ +public class BaseEnvironmentManager +{ + /* The Job Manager that is applicable for this Environment Manager */ + private JobManager jobManager = null; + + public JobManager getJobManager() + { + return jobManager; + } + + protected void setJobManager(JobManager jobManager) + { + this.jobManager = jobManager; + } + + protected JobType getJobTemplate(String urlName, AdapterType adapterType, AdapterBaseEnvStoreOperations envStoreOps) + { + JobTemplate jobTemplate = getJobManager().getJobTemplateInfo(urlName); + + if (jobTemplate != null) + { + return envStoreOps.loadJobTemplateData(jobTemplate.getTemplateFileName(), getJobManager().getAdapterType(), getJobManager().getEnvironmentType()); + } + + // If we get here then no Job Template or Job Data has been found. Error already logged. + return null; + } +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseJobManager.java new file mode 100644 index 00000000..43860ac6 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseJobManager.java @@ -0,0 +1,316 @@ +/* + * BaseJobManager.java + * Created: 13 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.AdapterType; +import sif3.common.exception.MarshalException; +import sif3.common.exception.PersistenceException; +import sif3.common.exception.UnmarshalException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.persist.model.JobTemplate; +import sif3.common.persist.model.SIF3Job; +import sif3.common.persist.service.JobService; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; +import sif3.infra.common.env.types.ExtendedJobInfo; +import sif3.infra.common.env.types.ProviderEnvironment; +import sif3.infra.common.interfaces.JobManager; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; + +/** + * This method has some implementations of the JobManager interface that is common for all concrete implementations. This is an abstract + * class that allows some interface methods to be implemented here but others in the concrete implementation. This class further has some + * utility methods beyond the interface method implementations. + * + * @author Joerg Huber + * + */ +public abstract class BaseJobManager implements JobManager +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private JobService service = new JobService(); + private EnvironmentInfo environmentInfo = null; + + protected BaseJobManager(EnvironmentInfo environmentInfo) + { + super(); + this.environmentInfo = environmentInfo; + } + + protected JobService getService() + { + return service; + } + + /*-----------------------*/ + /*-- Interface Methods --*/ + /*-----------------------*/ + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getEnvironmentInfo() + */ + @Override + public EnvironmentInfo getEnvironmentInfo() + { + return environmentInfo; + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getEnvironmentType() + */ + @Override + public EnvironmentType getEnvironmentType() + { + return getEnvironmentInfo().getEnvironmentType(); + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getJobTemplateInfo(java.lang.String) + */ + @Override + public JobTemplate getJobTemplateInfo(String urlName) + { + try + { + return service.getJobTemplateForAdapter(urlName, getAdapterType()); + } + catch (Exception ex) // error is already logged. + { + return null; + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getJob(java.lang.String) + */ + @Override + public SIF3Job getJob(String jobID) throws PersistenceException + { + if(StringUtils.isEmpty(jobID)) + { + return null; + } + + JobService service = getService(); + return service.getJobByUUID(jobID, getAdapterType()); + } + + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#saveNewJob(sif3.infra.common.model.JobType) + */ + @Override + public void saveNewJob(JobType newJob, String serviceName, SIFZone zone, SIFContext context, String environmentID, String fingerprint, boolean consumerRequested) throws PersistenceException + { + String jobXML = null; + try + { + jobXML = createJobXML(newJob); + } + catch (MarshalException ex) + { + throw new PersistenceException(ex); + } + + JobService service = getService(); + SIF3Job sif3Job = new SIF3Job(); + sif3Job.setAdapterType(getAdapterType().name()); + sif3Job.setContextID(context.getId()); + sif3Job.setZoneID(zone.getId()); + sif3Job.setEnvironmentID(environmentID); + sif3Job.setFingerprint(fingerprint); + sif3Job.setJobID(newJob.getId()); + sif3Job.setCreated(newJob.getCreated().getTime()); + sif3Job.setServiceName(serviceName); + + if (newJob.getTimeout() != null) + { + sif3Job.setTimeoutPeriod(newJob.getTimeout().toString()); + } + if (newJob.getState() != null) + { + sif3Job.setCurrentState(newJob.getState().name()); + } + + sif3Job.setJobXML(jobXML); + + service.createJob(sif3Job); + + if (addToChangeLog()) + { + service.createJobEvent(sif3Job, EventAction.CREATE, true, consumerRequested); + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#removeJob(java.lang.String, sif3.common.model.SIFZone, sif3.common.model.SIFContext, java.lang.String, java.lang.String) + */ + @Override + public boolean removeJob(String jobID, boolean consumerRequested) throws PersistenceException + { + JobService service = getService(); + SIF3Job sif3Job = service.getJobByUUID(jobID, getAdapterType()); + + if (sif3Job == null) // does not exist => We are done. + { + return false; + } + + // Remove job + service.removeJob(sif3Job.getInternalID()); + + if (addToChangeLog()) + { + service.createJobEvent(sif3Job, EventAction.DELETE, true, consumerRequested); + } + + return true; + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#retrieveJobs(java.lang.String, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo) + */ + @Override + public JobCollectionType retrieveJobs(String serviceName, String fingerprint, SIFZone zone, SIFContext context, PagingInfo pagingInfo) throws PersistenceException + { + JobService service = getService(); + List sif3Jobs = service.retrieveJobs(serviceName, fingerprint, zone.getId(), context.getId(), pagingInfo, getAdapterType()); + + JobCollectionType jobCollection = new JobCollectionType(); + for (SIF3Job job : sif3Jobs) + { + JobType jobType = unmarschalJobXML(job.getJobXML()); + if (jobType == null) // should not really be. All we can do is create an empty jobType with the refId + { + jobType = new JobType(); + jobType.setId(job.getJobID()); + } + jobCollection.getJob().add(jobType); + } + + return jobCollection; + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#retrieveJob(java.lang.String) + */ + @Override + public JobType retrieveJob(String jobID) throws PersistenceException + { + SIF3Job sif3Job = getJob(jobID); + if (sif3Job == null) // does not exist => We are done. + { + return null; + } + else + { + return unmarschalJobXML(sif3Job.getJobXML()); + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getJobInfo(java.lang.String) + */ + @Override + public ExtendedJobInfo getJobInfo(String jobID) throws PersistenceException + { + SIF3Job sif3Job = getJob(jobID); + if (sif3Job != null) + { + return new ExtendedJobInfo(sif3Job); + } + return null; + } + + /*-------------------------------*/ + /*-- Protected Utility Methods --*/ + /*-------------------------------*/ + + /** + * Marshal a Job Object to XML. + * + * @param job The Job Object to marshal to XML. + * + * @return The final XML string. + * + * @throws MarshalException Invalid data. Could not marshal. Error logged already. + */ + protected String createJobXML(JobType job) throws MarshalException + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + try + { + return marshaller.marshalToXML(job); + } + catch (UnsupportedMediaTypeExcpetion ex) // cannot happen here + { + return null; + } + } + + /** + * Unmarshal the Job XML to a proper Job. + * + * @param jobXML The JobXML to unmarshal to Job Object. + * + * @return The Job Object. Can be null if XML is null or empty or if the XML is not a JobType. In both cases an error is logged. + * + * @throws MarshalException Invalid data. Could not marshal. Error logged already. + */ + protected JobType unmarschalJobXML(String jobXML) + { + if (jobXML == null) + { + logger.error("Job XML is null. Cannot unmrshal => Return null."); + return null; + } + InfraUnmarshalFactory unmarshaller = new InfraUnmarshalFactory(); + try + { + return (JobType)unmarshaller.unmarshalFromXML(jobXML, JobType.class); + } + catch (UnmarshalException ex) // cannot happen here + { + logger.error("Invalid Job XML. Cannot unmrshal => Return null.\nOffending XML:\n"+jobXML); + return null; + } + catch (UnsupportedMediaTypeExcpetion e) // should not happen + { + return null; + } + } + + private boolean addToChangeLog() + { + // If we are a provider and jobs are enabled then we add the job to the change table. + return ((getEnvironmentInfo().getAdapterType() != AdapterType.CONSUMER) && (((ProviderEnvironment)getEnvironmentInfo()).isJobEnabled())); + } +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseProviderJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseProviderJobManager.java new file mode 100644 index 00000000..e64edacc --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BaseProviderJobManager.java @@ -0,0 +1,266 @@ +/* + * BaseProviderJobManager.java + * Created: 11 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.exception.PersistenceException; +import sif3.common.header.HeaderValues.EventAction; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.persist.model.SIF3JobEvent; +import sif3.common.persist.service.JobService; +import sif3.common.utils.UUIDGenerator; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.env.types.ExtendedJobInfo; +import sif3.infra.common.env.types.ProviderEnvironment; +import sif3.infra.common.interfaces.ProviderJobManager; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobStateType; +import sif3.infra.common.model.JobType; +import sif3.infra.common.model.PhaseStateType; +import sif3.infra.common.model.PhaseType; +import sif3.infra.common.model.StateCollectionType; +import sif3.infra.common.model.StateType; + +/** + * @author Joerg Huber + * + */ +public abstract class BaseProviderJobManager extends BaseJobManager implements ProviderJobManager +{ + protected BaseProviderJobManager(EnvironmentInfo environmentInfo) + { + super(environmentInfo); + } + + /*-----------------------*/ + /*-- Interface Methods --*/ + /*-----------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.ProviderJobManager#updateJobState(java.lang.String, sif3.common.CommonConstants.JobState) + */ + @Override + public void updateJobState(String jobID, JobState newState) throws PersistenceException + { + if (newState != null) + { + ExtendedJobInfo jobInfo = getJobInfo(jobID); + if ((jobInfo != null) && (jobInfo.isXMLValid())) // we are all good to go + { + jobInfo.getDBJob().setCurrentState(newState.name()); + jobInfo.getXMLJob().setState(JobStateType.valueOf(newState.name())); + updateJob(jobInfo, false); + } + else if (jobInfo.isXMLValid()) // we don't have a valid XML. Cannot update state + { + throw new PersistenceException("Failed to retrieve Job "+jobID+" from underlying Data Store. The Job XML is invalid."); + } + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.ProviderJobManager#updatePhaseState(java.lang.String, java.lang.String, sif3.common.CommonConstants.PhaseState) + */ + @Override + public void updatePhaseState(String jobID, String phaseName, PhaseState newState) throws PersistenceException + { + if ((phaseName != null) && (newState != null)) + { + StateType newPhaseState = new StateType(); + newPhaseState.setType(PhaseStateType.valueOf(newState.name())); + + ExtendedJobInfo jobInfo = getJobInfo(jobID); + if ((jobInfo != null) && (jobInfo.isXMLValid())) // we are all good to go + { + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase != null) // add the state + { + addStateToPhase(phase, newPhaseState); + updateJob(jobInfo, false); + } + } + else if (jobInfo.isXMLValid()) // we don't have a valid XML. Cannot update state + { + throw new PersistenceException("Failed to retrieve Job "+jobID+" from underlying Data Store. The Job XML is invalid."); + } + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.ProviderJobManager#updateJobStateAndPhaseState(java.lang.String, sif3.common.CommonConstants.JobState, java.lang.String, sif3.common.CommonConstants.PhaseState) + */ + @Override + public void updateJobStateAndPhaseState(String jobID, JobState newJobState, String phaseName, PhaseState newPhaseState) throws PersistenceException + { + ExtendedJobInfo jobInfo = getJobInfo(jobID); + if ((jobInfo != null) && (jobInfo.isXMLValid())) // we are all good to go + { + boolean valuesUpdated = false; + if (newJobState != null) + { + jobInfo.getDBJob().setCurrentState(newJobState.name()); + jobInfo.getXMLJob().setState(JobStateType.valueOf(newJobState.name())); + valuesUpdated = true; + } + if ((phaseName != null) && (newPhaseState != null)) + { + StateType phaseState = new StateType(); + phaseState.setType(PhaseStateType.valueOf(newPhaseState.name())); + PhaseType phase = getPhase(jobInfo.getXMLJob(), phaseName); + if (phase != null) // add the state + { + addStateToPhase(phase, phaseState); + valuesUpdated = true; + } + } + + if (valuesUpdated) + { + updateJob(jobInfo, false); + } + } + else if (jobInfo.isXMLValid()) // we don't have a valid XML. Cannot update state + { + throw new PersistenceException("Failed to retrieve Job "+jobID+" from underlying Data Store. The Job XML is invalid."); + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.ProviderJobManager#updateJob(sif3.infra.common.env.types.ExtendedJobInfo) + */ + @Override + public void updateJob(ExtendedJobInfo jobInfo, boolean consumerRequested) throws PersistenceException + { + JobService service = getService(); + if (jobInfo != null) + { + jobInfo.getXMLJob().setLastModified(Calendar.getInstance()); + if (!jobInfo.marshalXMLintoDBJob()) + { + throw new PersistenceException("Failed to marshal Job into XML after an update."); + } + + service.updateJob(jobInfo.getDBJob()); + + if (getJobEnabled()) // Create UPDATE event. + { + service.createJobEvent(jobInfo.getDBJob(), EventAction.UPDATE, true, consumerRequested); + } + } + } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.ProviderJobManager#retrieveJobChangesSince(java.util.Date, java.lang.String, java.lang.String, sif3.common.model.SIFZone, sif3.common.model.SIFContext, sif3.common.model.PagingInfo) + */ + @Override + public JobCollectionType retrieveJobChangesSince(Date changesSince, + String serviceName, + String fingerprint, + SIFZone zone, + SIFContext context, + PagingInfo pagingInfo) + throws PersistenceException + { + JobService service = getService(); + List sifJobEvents = service.retrieveJobEventsSince(changesSince, serviceName, fingerprint, zone.getId(), context.getId(), pagingInfo, getAdapterType()); + + JobCollectionType jobList = new JobCollectionType(); + for (SIF3JobEvent jobEvent : sifJobEvents) + { + JobType job = new JobType(); + if (jobEvent.getEventType().equals("D")) // Delete Event => Only set RefID + { + job.setId(jobEvent.getJobID()); + } + else // We need to unmarshal the XML into an object. + { + job = unmarschalJobXML(jobEvent.getJobXML()); + } + + if (job != null) + { + jobList.getJob().add(job); + } + } + + return jobList; + } + + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + private PhaseType getPhase(JobType job, String phaseName) + { + // Start iterating through the phases until it matches. + if (job.getPhases() == null) // no phases known + { + return null; + } + + for (PhaseType phase : job.getPhases().getPhase()) + { + if (phase.getName().equals(phaseName)) // found the applicable phase + { + return phase; + } + } + + // if we get here then we haven't found the correct phase + return null; + } + + private StateType addStateToPhase(PhaseType phase, StateType newState) + { + if (phase != null) // add the state + { + StateCollectionType states = phase.getStates(); + if (states == null) + { + states = new StateCollectionType(); + phase.setStates(states); + } + + StateType state = new StateType(); + state.setCreated(Calendar.getInstance()); + state.setLastModified(Calendar.getInstance()); + state.setId(StringUtils.isEmpty(newState.getId()) ? UUIDGenerator.getUUID() : newState.getId()); + state.setType(newState.getType()); + states.getState().add(state); + + return state; + } + + return null; + } + + // Only do events/changes since entries if jobs are enabled. + private boolean getJobEnabled() + { + return ((ProviderEnvironment)getEnvironmentInfo()).isJobEnabled(); + } + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderEnvironmentManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderEnvironmentManager.java index 0c618a0f..76d9810b 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderEnvironmentManager.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderEnvironmentManager.java @@ -27,6 +27,7 @@ import au.com.systemic.framework.utils.AdvancedProperties; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.AdapterType; import sif3.common.exception.PersistenceException; import sif3.common.model.EnvironmentKey; import sif3.common.model.security.TokenInfo; @@ -36,9 +37,11 @@ import sif3.infra.common.env.types.EnvironmentInfo; import sif3.infra.common.env.types.ProviderEnvironment; import sif3.infra.common.interfaces.ClientEnvironmentManager; +import sif3.infra.common.interfaces.ProviderJobManager; import sif3.infra.common.model.EnvironmentType; import sif3.infra.common.model.InfrastructureServiceType; import sif3.infra.common.model.InfrastructureServicesType; +import sif3.infra.common.model.JobType; import sif3.infra.common.utils.SIFSessionUtils; /** @@ -50,7 +53,7 @@ * * @author Joerg Huber */ -public class BrokeredProviderEnvironmentManager implements ClientEnvironmentManager +public class BrokeredProviderEnvironmentManager extends BaseEnvironmentManager implements ClientEnvironmentManager { protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -120,6 +123,11 @@ public AdvancedProperties getServiceProperties() { return envOps.getServiceProperties(); } + + public ProviderJobManager getJobManager() + { + return (ProviderJobManager)super.getJobManager(); + } /* (non-Javadoc) * @see sif3.infra.common.interfaces.EnvironmentManager#getSessionBySessionToken(java.lang.String) @@ -309,11 +317,26 @@ public boolean removeEnvironment(EnvironmentKey environmentKey) return true; // no such session/environment => no action taken. } } + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.EnvironmentManager#getJobTemplate(java.lang.String) + */ + @Override + public JobType getJobTemplate(String urlName) + { + return getJobTemplate(urlName, getAdapterType(), envOps); + } + + @Override + public AdapterType getAdapterType() + { + return AdapterType.PROVIDER; + } @Override public String toString() { - return "ConsumerEnvironmentManager [sif3Session=" + sif3Session + "]"; + return "BrokeredEnvironmentManager [sif3Session=" + sif3Session + "]"; } /*---------------------*/ @@ -327,6 +350,7 @@ private BrokeredProviderEnvironmentManager(String adapterFileNameWithoutExt) super(); this.adapterFileNameWithoutExt = adapterFileNameWithoutExt; this.envOps = new BrokeredProviderEnvStoreOps(adapterFileNameWithoutExt); + setJobManager(new BrokeredProviderJobManager(getEnvironmentInfo())); } private boolean existsSIF3SessionInSessionStore() diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderJobManager.java new file mode 100644 index 00000000..bdb2adb7 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/BrokeredProviderJobManager.java @@ -0,0 +1,54 @@ +/* + * BrokeredProviderJobManager.java + * Created: 13 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import sif3.common.CommonConstants.AdapterType; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.interfaces.ProviderJobManager; + +/** + * @author Joerg Huber + * + */ +public class BrokeredProviderJobManager extends BaseProviderJobManager implements ProviderJobManager +{ + /* + * Constructor: Create an Job Manager for the given environment info. + */ + public BrokeredProviderJobManager(EnvironmentInfo environmentInfo) + { + super(environmentInfo); + } + + /*-----------------------*/ + /*-- Interface Methods --*/ + /*-----------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getAdapterType() + */ + @Override + public AdapterType getAdapterType() + { + return AdapterType.PROVIDER; + } + + /*---------------------*/ + /*-- Private methods --*/ + /*---------------------*/ +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerEnvironmentManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerEnvironmentManager.java index 1d03edbe..c535ef55 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerEnvironmentManager.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerEnvironmentManager.java @@ -27,6 +27,7 @@ import au.com.systemic.framework.utils.AdvancedProperties; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.AdapterType; import sif3.common.exception.PersistenceException; import sif3.common.model.EnvironmentKey; import sif3.common.model.security.TokenInfo; @@ -38,6 +39,7 @@ import sif3.infra.common.model.EnvironmentType; import sif3.infra.common.model.InfrastructureServiceType; import sif3.infra.common.model.InfrastructureServicesType; +import sif3.infra.common.model.JobType; import sif3.infra.common.utils.SIFSessionUtils; /** @@ -48,7 +50,7 @@ * * @author Joerg Huber */ -public class ConsumerEnvironmentManager implements ClientEnvironmentManager +public class ConsumerEnvironmentManager extends BaseEnvironmentManager implements ClientEnvironmentManager { protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -119,7 +121,12 @@ public AdvancedProperties getServiceProperties() return envOps.getServiceProperties(); } - /* (non-Javadoc) + public ConsumerJobManager getJobManager() + { + return (ConsumerJobManager)super.getJobManager(); + } + + /* (non-Javadoc) * @see sif3.infra.common.interfaces.EnvironmentManager#getSessionBySessionToken(java.lang.String) */ @Override @@ -308,7 +315,23 @@ public boolean removeEnvironment(EnvironmentKey environmentKey) } } - @Override + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.EnvironmentManager#getJobTemplate(java.lang.String) + */ + @Override + public JobType getJobTemplate(String urlName) + { + return getJobTemplate(urlName, getAdapterType(), envOps); + } + + @Override + public AdapterType getAdapterType() + { + return AdapterType.CONSUMER; + } + + + @Override public String toString() { return "ConsumerEnvironmentManager [sif3Session=" + sif3Session + "]"; @@ -325,6 +348,7 @@ private ConsumerEnvironmentManager(String adapterFileNameWithoutExt) super(); this.adapterFileNameWithoutExt = adapterFileNameWithoutExt; this.envOps = new ConsumerEnvironmentStoreOperations(adapterFileNameWithoutExt); + setJobManager(new ConsumerJobManager(getEnvironmentInfo())); } private boolean existsSIF3SessionInSessionStore() diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerJobManager.java new file mode 100644 index 00000000..248c9685 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ConsumerJobManager.java @@ -0,0 +1,53 @@ +/* + * ConsumerJobManager.java + * Created: 13 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import sif3.common.CommonConstants.AdapterType; +import sif3.infra.common.env.types.EnvironmentInfo; + +/** + * @author Joerg Huber + * + */ +public class ConsumerJobManager extends BaseConsumerJobManager implements sif3.infra.common.interfaces.ConsumerJobManager +{ + /* + * Constructor: Create an Job manager for the given environment. + */ + public ConsumerJobManager(EnvironmentInfo environmentInfo) + { + super(environmentInfo); + } + + /*-----------------------*/ + /*-- Interface Methods --*/ + /*-----------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getAdapterType() + */ + @Override + public AdapterType getAdapterType() + { + return AdapterType.CONSUMER; + } + + /*---------------------*/ + /*-- Private methods --*/ + /*---------------------*/ +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderEnvironmentManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderEnvironmentManager.java index dda05ec9..cd515439 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderEnvironmentManager.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderEnvironmentManager.java @@ -24,6 +24,7 @@ import au.com.systemic.framework.utils.AdvancedProperties; import au.com.systemic.framework.utils.StringUtils; +import sif3.common.CommonConstants.AdapterType; import sif3.common.exception.PersistenceException; import sif3.common.model.EnvironmentKey; import sif3.common.model.security.TokenInfo; @@ -32,7 +33,9 @@ import sif3.infra.common.env.ops.DirectProviderEnvStoreOps; import sif3.infra.common.env.types.EnvironmentInfo; import sif3.infra.common.interfaces.EnvironmentManager; +import sif3.infra.common.interfaces.ProviderJobManager; import sif3.infra.common.model.EnvironmentType; +import sif3.infra.common.model.JobType; import sif3.infra.common.utils.SIFSessionUtils; /** @@ -43,7 +46,7 @@ * * @author Joerg Huber */ -public class DirectProviderEnvironmentManager implements EnvironmentManager +public class DirectProviderEnvironmentManager extends BaseEnvironmentManager implements EnvironmentManager { protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -58,7 +61,7 @@ public class DirectProviderEnvironmentManager implements EnvironmentManager /* Key=SecurityToken for environment & consumer, Data: SessionToken relating to securityToken. Used for Bearer security tokens*/ private HashMap secTokenSession = new HashMap(); - + private static DirectProviderEnvironmentManager instance = null; /** @@ -117,6 +120,11 @@ public AdvancedProperties getServiceProperties() return envOps.getServiceProperties(); } + public ProviderJobManager getJobManager() + { + return (ProviderJobManager)super.getJobManager(); + } + /* * (non-Javadoc) * @see sif3.infra.common.interfaces.EnvironmentManager#getSessionBySessionToken(java.lang.String) @@ -193,7 +201,16 @@ public boolean updateSessionSecurityInfo(String sessionToken, String securityTok } - /* + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.EnvironmentManager#getJobTemplate(java.lang.String) + */ + @Override + public JobType getJobTemplate(String urlName) + { + return getJobTemplate(urlName, getAdapterType(), envOps); + } + + /* * (non-Javadoc) * @see sif3.infra.common.interfaces.EnvironmentManager#getEnvironmentType() */ @@ -202,12 +219,19 @@ public sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType getEnvironmen { return getEnvironmentInfo().getEnvironmentType(); } + + @Override + public AdapterType getAdapterType() + { + return AdapterType.ENVIRONMENT_PROVIDER; + } + /*--------------------------*/ /*-- Other Public Methods --*/ /*--------------------------*/ - /** + /** * This method will create an environment on the provider. The environment will be created based on the given input environment * information. If this operation fails an error is logged and null is returned. The environment will be created and all the * information according to the SIF3 spec is returned (i.e. Session Token, Environment Id etc). The associated @@ -435,6 +459,7 @@ private DirectProviderEnvironmentManager(String adapterFileNameWithoutExt) super(); this.adapterFileNameWithoutExt = adapterFileNameWithoutExt; this.envOps = new DirectProviderEnvStoreOps(adapterFileNameWithoutExt); + setJobManager(new DirectProviderJobManager(getEnvironmentInfo())); } /* diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderJobManager.java new file mode 100644 index 00000000..64e6675c --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/DirectProviderJobManager.java @@ -0,0 +1,54 @@ +/* + * DirectProviderJobManager.java + * Created: 13 Feb 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.mgr; + +import sif3.common.CommonConstants.AdapterType; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.interfaces.ProviderJobManager; + +/** + * @author Joerg Huber + * + */ +public class DirectProviderJobManager extends BaseProviderJobManager implements ProviderJobManager +{ + /* + * Constructor: Create an Job manager for the given environment. + */ + public DirectProviderJobManager(EnvironmentInfo environmentInfo) + { + super(environmentInfo); + } + + /*-----------------------*/ + /*-- Interface Methods --*/ + /*-----------------------*/ + + /* (non-Javadoc) + * @see sif3.infra.common.interfaces.JobManager#getAdapterType() + */ + @Override + public AdapterType getAdapterType() + { + return AdapterType.ENVIRONMENT_PROVIDER; + } + + /*---------------------*/ + /*-- Private methods --*/ + /*---------------------*/ +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ProviderManagerFactory.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ProviderManagerFactory.java index 860c06e8..8d8ad61b 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ProviderManagerFactory.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/mgr/ProviderManagerFactory.java @@ -28,7 +28,7 @@ /** * Within this framework there are many places where an environment manager is required. Because many of these implementations are * generic for consumers and providers it is essential that code of these implementations deal with interfaces rather than with - * hard-wired concrete classes. At runtime though a concrete implemementation is required and for that this factory shall be used. + * hard-wired concrete classes. At runtime though a concrete implementation is required and for that this factory shall be used. * * @author Joerg Huber * diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/AdapterBaseEnvStoreOperations.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/AdapterBaseEnvStoreOperations.java index 11b64ac5..c9080024 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/AdapterBaseEnvStoreOperations.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/AdapterBaseEnvStoreOperations.java @@ -39,6 +39,7 @@ import sif3.infra.common.env.types.ConsumerEnvironment; import sif3.infra.common.env.types.EnvironmentInfo; import sif3.infra.common.model.EnvironmentType; +import sif3.infra.common.model.JobType; /** * Helper class for some operations required by the Environment Store (Consumer or Provider). Only used internal to the framework. @@ -93,6 +94,11 @@ public boolean existEnvironmentTemplate(String templateFileName, AdapterType ada return FileAndFolderUtils.doesFileExist(getFullTemplateFileName(templateFileName, adapterType, envType)); } + public boolean existJobTemplate(String jobTemplateFileName, AdapterType adapterType, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType envType) + { + return FileAndFolderUtils.doesFileExist(getFullJobTemplateFileName(jobTemplateFileName, adapterType, envType)); + } + /** * * @param envFileName The name of the environment file to load from template directory. @@ -104,6 +110,17 @@ public EnvironmentType loadTemplateEnvironmentData(String envFileName, AdapterTy return loadEnvironmentDataFromFile(getFullTemplateFileName(envFileName, adapterType, envType)); } + /** + * + * @param jobTemplateFileName The name of the job template file to load from template/job directory. + * + * @return Null => Failed to load data due to some error. See error log for details. + */ + public JobType loadJobTemplateData(String jobTemplateFileName, AdapterType adapterType, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType envType) + { + return loadJobDataFromFile(getFullJobTemplateFileName(jobTemplateFileName, adapterType, envType)); + } + /** * Returns a SIF3Session for the given session token. If there is no such session then null is returned. * @@ -322,25 +339,22 @@ public boolean updateSessionSecurityInfo(String sessionToken, String securityTok */ public EnvironmentType loadEnvironmentFromString(String envrionmentData) { - if (envrionmentData != null) - { - InfraUnmarshalFactory unmarshaller = new InfraUnmarshalFactory(); - try - { - return (EnvironmentType) unmarshaller.unmarshalFromXML(envrionmentData, EnvironmentType.class); - } - catch (Exception ex) - { - logger.error("Failed to successfully parse the XML content:\n"+envrionmentData); - return null; - } - } - else - { - logger.error("The provided environment data to parse is null."); - return null; - } + return (EnvironmentType)loadInfraObjectFromString(envrionmentData, EnvironmentType.class); } + + /** + * This method takes the given XML job string and converts it into a Infrastructure Job Type. If the string is null or + * invalid then an error is logged and null is returned. + * + * @param jobTemplateData XML formatted environment data as of the SIF3 spec. + * + * @return See desc. + */ + public JobType loadJobTemplateFromString(String jobTemplateData) + { + return (JobType)loadInfraObjectFromString(jobTemplateData, JobType.class); + } + /** * Store the environment data (inclusive session info) to workstore for the given SIF3 session. All property of the session @@ -421,36 +435,34 @@ protected String getXMLFromFile(String fileName) } /** - * @param fullName Full name of file (path, name and extension. It is expected this file to have XML content. + * @param fullName Full name of file (path, name and extension). It is expected this file to have XML content. * * @return Null => Failed to load data due to some error. See error log for details. */ protected EnvironmentType loadEnvironmentDataFromFile(String fullFileName) { - if (FileAndFolderUtils.doesFileExist(fullFileName)) - { - String xmlStr = getXMLFromFile(fullFileName); - if (xmlStr != null) - { - return loadEnvironmentFromString(xmlStr); - } - else - { - logger.error("The XML file "+fullFileName+" cannot be read. Check permissions and content."); - return null; - } - } - else - { - logger.error("There is no XML file: "+fullFileName); - return null; - } + return (EnvironmentType)loadXMLDataFromFile(fullFileName, EnvironmentType.class); } - + + /** + * @param fullName Full name of file (path, name and extension). It is expected this file to have XML content. + * + * @return Null => Failed to load data due to some error. See error log for details. + */ + protected JobType loadJobDataFromFile(String fullFileName) + { + return (JobType)loadXMLDataFromFile(fullFileName, JobType.class); + } + protected String getFullTemplateFileName(String envFileName, AdapterType adapterType, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType envType) { return getFullFileName(getEnvironmentStore().getFullTemplateDirName(adapterType==AdapterType.CONSUMER, envType), envFileName); } + + protected String getFullJobTemplateFileName(String jobFileName, AdapterType adapterType, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType envType) + { + return getFullFileName(getEnvironmentStore().getFullJobTemplateDirName(adapterType == AdapterType.CONSUMER, envType), jobFileName); + } protected String getFullFileName(String path, String envFileName) { @@ -468,4 +480,66 @@ protected void updateSIF3SessionForClients(SIF3Session sif3Session) sif3Session.setAuthenticationMethod(getEnvironmentInfo().getAuthMethod()); } } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + + /** + * @param fullName Full name of file (path, name and extension). It is expected this file to have XML content. + * + * @return Null => Failed to load data due to some error. See error log for details. + */ + private Object loadXMLDataFromFile(String fullFileName, Class clazz) + { + if (FileAndFolderUtils.doesFileExist(fullFileName)) + { + String xmlStr = getXMLFromFile(fullFileName); + if (xmlStr != null) + { + return loadInfraObjectFromString(xmlStr, clazz); + } + else + { + logger.error("The XML file "+fullFileName+" cannot be read. Check permissions and content."); + return null; + } + } + else + { + logger.error("There is no XML file: "+fullFileName); + return null; + } + } + + + /* + * This method takes the given XML string and converts it into a Infrastructure object. If the string is null or + * invalid then an error is logged and null is returned. + * + * @param xmlData XML formatted data of a infrastructure object as of the SIF3 spec. + * + * @return See desc. + */ + private Object loadInfraObjectFromString(String xmlData, Class clazz) + { + if (xmlData != null) + { + InfraUnmarshalFactory unmarshaller = new InfraUnmarshalFactory(); + try + { + return unmarshaller.unmarshalFromXML(xmlData, clazz); + } + catch (Exception ex) + { + logger.error("Failed to successfully parse the XML content:\n"+xmlData); + return null; + } + } + else + { + logger.error("The provided environment data to parse is null."); + return null; + } + } } diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/DirectProviderEnvStoreOps.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/DirectProviderEnvStoreOps.java index afd00ab8..7cd2621c 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/DirectProviderEnvStoreOps.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/ops/DirectProviderEnvStoreOps.java @@ -41,6 +41,7 @@ import sif3.infra.common.model.EnvironmentTypeType; import sif3.infra.common.model.InfrastructureServiceType; import sif3.infra.common.model.InfrastructureServicesType; +import sif3.infra.common.model.JobType; /** * This class implements operations required by a direct environment provider. They are quite distinct and therefore warrant having its own @@ -75,7 +76,20 @@ public boolean existEnvironmentTemplate(String templateFileName) return existEnvironmentTemplate(templateFileName, AdapterType.ENVIRONMENT_PROVIDER, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType.DIRECT); } - /** + /** + * Check of a job template for the given filename exists. TRUE: it exists, FALSE it doesn't. + * + * @param jobTemplateFileName The name of the job template file to check for in template/job directory. This name must + * include the extension ".xml". + * + * @return TRUE: Job Template exists. FALSE: The job template for the given file name doen't exists. + */ + public boolean existJobTemplate(String jobTemplateFileName) + { + return existJobTemplate(jobTemplateFileName, AdapterType.ENVIRONMENT_PROVIDER, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType.DIRECT); + } + + /** * This method loads the given environment from the template store and returns it. If no such environment exists then null * is returned. * @@ -88,6 +102,19 @@ public EnvironmentType loadEnvironmentFromTemplate(String envFileName) return loadTemplateEnvironmentData(envFileName, AdapterType.ENVIRONMENT_PROVIDER, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType.DIRECT); } + /** + * This method loads the given job from the template store and returns it. If no such job exists then null + * is returned. + * + * @param jobTemplateFileName The name of the job template file to load from template directory. This name must include the extension ".xml". + * + * @return Null => Failed to load data due to some error. See error log for details. + */ + public JobType loadJobFromTemplate(String jobTemplateFileName) + { + return loadJobTemplateData(jobTemplateFileName, AdapterType.ENVIRONMENT_PROVIDER, sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType.DIRECT); + } + /** * Checks if an environment does already exists in the workstore for the given environmentKey. * diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/AdapterEnvironmentStore.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/AdapterEnvironmentStore.java index 9df692b6..82245407 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/AdapterEnvironmentStore.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/AdapterEnvironmentStore.java @@ -2,7 +2,7 @@ * AdapterEnvironmentStore.java * Created: 11/02/2014 * - * Copyright 2014 Systemic Pty Ltd + * Copyright 2014-2017 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.io.Serializable; import java.net.URI; +import java.util.List; import javax.ws.rs.core.MediaType; @@ -32,6 +33,7 @@ import sif3.common.CommonConstants; import sif3.common.CommonConstants.AdapterType; import sif3.common.CommonConstants.AuthenticationType; +import sif3.common.CommonConstants.JobState; import sif3.common.CommonConstants.QueuePollingType; import sif3.common.CommonConstants.QueueStrategy; import sif3.common.header.HeaderProperties; @@ -47,7 +49,6 @@ */ public class AdapterEnvironmentStore implements Serializable { - private static final long serialVersionUID = 34177453457564336L; protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -57,6 +58,7 @@ public class AdapterEnvironmentStore implements Serializable private static final String CONSUMER_DIR_NAME = "consumer"; private static final String DIRECT_DIR_NAME = "direct"; private static final String BROKERED_DIR_NAME = "brokered"; + private static final String JOB_DIR_NAME = "job"; private static final String XML = "XML"; private static final String JSON = "JSON"; @@ -124,11 +126,23 @@ public String getFullTemplateDirName(boolean isConsumer, EnvironmentType envType } } + public String getFullJobTemplateDirName(boolean isConsumer, EnvironmentType envType) + { + if (isConsumer) + { + return getEnvDirName()+"/"+TEMPLATE_DIR_NAME+"/"+JOB_DIR_NAME; + } + else // at the moment it is the same structure as with a consumer but that might change in future. + { + return getEnvDirName()+"/"+TEMPLATE_DIR_NAME+"/"+JOB_DIR_NAME; + } + } + public String getEnvDirName() { return getEnvironmentBaseDirName()+"/"+((getEnvironment().getAdapterType() == AdapterType.CONSUMER) ? CONSUMER_DIR_NAME : PROVIDER_DIR_NAME ); } - + @Override public String toString() { @@ -179,6 +193,14 @@ private void readConfig(String adapterFileNameWithoutExt) { errors = true; } + else + { + // Check and create Job Template directory + if (!checkAndCreateDir(getFullJobTemplateDirName(isConsumer, environment.getEnvironmentType()))) + { + errors = true; + } + } } // Media Type @@ -502,30 +524,34 @@ private boolean loadProviderInfo(AdvancedProperties props, ProviderEnvironment e // Secure URL Base URL for connectors String secureConnectorBaseURLStr = props.getPropertyAsString("env.connector.url.secure", null); - if (StringUtils.isEmpty(connectorBaseURLStr) && StringUtils.isEmpty(secureConnectorBaseURLStr)) + // In a DIRECT environment we need at least the connector base URL or secure connector base URL + if (envInfo.getEnvironmentType() == EnvironmentType.DIRECT) { - logger.error("env.connector.url AND env.connector.url.secure for "+getAdapterFileNameWithoutExt()+".properties is missing. You must provide at least one of these URLs for them."); - errorsFound = true; + if (StringUtils.isEmpty(connectorBaseURLStr) && StringUtils.isEmpty(secureConnectorBaseURLStr)) + { + logger.error("env.connector.url AND env.connector.url.secure for "+getAdapterFileNameWithoutExt()+".properties is missing. You must provide at least one of these URLs for them."); + errorsFound = true; + } + else + { + if (StringUtils.notEmpty(connectorBaseURLStr)) + { + envInfo.setConnectorBaseURI(cleanURI(connectorBaseURLStr, "env.connector.url")); + if (envInfo.getConnectorBaseURI() == null) + { + errorsFound = true; + } + } + if (StringUtils.notEmpty(secureConnectorBaseURLStr)) + { + envInfo.setSecureConnectorBaseURI(cleanURI(secureConnectorBaseURLStr, "env.connector.url.secure")); + if (envInfo.getSecureConnectorBaseURI() == null) + { + errorsFound = true; + } + } + } } - else - { - if (StringUtils.notEmpty(connectorBaseURLStr)) - { - envInfo.setConnectorBaseURI(cleanURI(connectorBaseURLStr, "env.connector.url")); - if (envInfo.getConnectorBaseURI() == null) - { - errorsFound = true; - } - } - if (StringUtils.notEmpty(secureConnectorBaseURLStr)) - { - envInfo.setSecureConnectorBaseURI(cleanURI(secureConnectorBaseURLStr, "env.connector.url.secure")); - if (envInfo.getSecureConnectorBaseURI() == null) - { - errorsFound = true; - } - } - } envInfo.setBaseURI(cleanURI(props.getPropertyAsString("env.baseURI", null), "env.baseURI")); if (envInfo.getEnvironmentType() == EnvironmentType.BROKERED) // The baseURI is required @@ -549,6 +575,37 @@ private boolean loadProviderInfo(AdvancedProperties props, ProviderEnvironment e // Allow access_token or URL envInfo.setAllowAuthOnURL(adapterProperties.getPropertyAsBool("adapter.authTokenOnURL.allowed", false)); + + // Functional Service Properties + envInfo.setJobEnabled(adapterProperties.getPropertyAsBool("job.enabled", false)); + envInfo.setJobEventFrequency(adapterProperties.getPropertyAsInt("job.event.frequency", 900)); + envInfo.setJobKeepDays(adapterProperties.getPropertyAsInt("job.stale.keep.days", 30)); + envInfo.setJobEventKeepDays(adapterProperties.getPropertyAsInt("job.changelog.keep.days", 30)); + envInfo.setJobHousekeepingCron(adapterProperties.getPropertyAsString("job.housekeeping.cron", ProviderEnvironment.JOB_HOUSEKEEPING_CRON)); + + // Get Job End States + List endStateStrArray = props.getFromCommaSeparated("job.endStates"); + if (endStateStrArray.isEmpty()) // not set or not provided + { + envInfo.getJobEndStates().add(JobState.COMPLETED); + envInfo.getJobEndStates().add(JobState.FAILED); + } + else if (!endStateStrArray.get(0).equalsIgnoreCase("NONE")) + { + for (String endState : endStateStrArray) + { + try + { + envInfo.getJobEndStates().add(JobState.valueOf(endState)); + } + catch (Exception ex) // unknown state defined in list. Log error but ignore the value and continue + { + logger.error("Property 'job.endStates' in "+getAdapterFileNameWithoutExt()+".properties holds an invalid Job State. Invalid value is: "+endState+"."); + errorsFound = true; + } + } + } + // Load custom response headers. errorsFound = errorsFound || loadCustomResponseHeaders(props, envInfo); diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ExtendedJobInfo.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ExtendedJobInfo.java new file mode 100644 index 00000000..efa050f7 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ExtendedJobInfo.java @@ -0,0 +1,188 @@ +/* + * ExtendedJobInfo.java + * Created: 12 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.env.types; + +import java.io.Serializable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sif3.common.exception.MarshalException; +import sif3.common.exception.UnmarshalException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.persist.model.SIF3Job; +import sif3.infra.common.conversion.InfraMarshalFactory; +import sif3.infra.common.conversion.InfraUnmarshalFactory; +import sif3.infra.common.model.JobType; + +/** + * This is a utility class that holds extended job information. In the functional service resource the job and related + * housekeeping data needs to be moved around. For efficiency reason, avoiding unnecessary DB accesses, this object can be used. + * + * @author Joerg Huber + */ +public class ExtendedJobInfo implements Serializable +{ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final long serialVersionUID = 2474434076004433430L; + + private SIF3Job dbJob = null; + private JobType xmlJob = null; + private boolean isXMLValid = true; + + public ExtendedJobInfo() + { + super(); + } + + /** + * This will set the dbJob property of this class but it will also unmarshal the dbJob.jobXML property into the + * xmlJob property of this class. If this succeeds then the property "xmlValid" is set to true. + * + * @param dbJob The job to be set. + */ + public ExtendedJobInfo(SIF3Job dbJob) + { + super(); + setDBJob(dbJob); + } + + public SIF3Job getDBJob() + { + return dbJob; + } + + /** + * This will set the dbJob property of this class but it will also unmarshal the dbJob.jobXML property into the + * xmlJob property of this class. If this succeeds then the property "xmlValid" is set to true. + * + * @param dbJob The job to be set. + */ + public void setDBJob(SIF3Job dbJob) + { + this.dbJob = dbJob; + setXMLJob(unmarschalJobXML(dbJob.getJobXML())); + } + + public JobType getXMLJob() + { + return xmlJob; + } + + public void setXMLJob(JobType xmlJob) + { + this.xmlJob = xmlJob; + } + + public boolean isXMLValid() + { + return isXMLValid; + } + + /** + * This method attempts to marshal the given JobXML into a String and then assign it to the dbJob.jobXML property. + * If it succeeds then true is returned. Any failure will return false and an error is logged. + * + * @return See Desc. + */ + public boolean marshalXMLintoDBJob() + { + if (isXMLValid()) + { + try + { + getDBJob().setJobXML(createJobXML(getXMLJob())); + return true; + } + catch (MarshalException ex) + { + logger.error(("Failed to marshal the Job Data into XML: "+ex.getMessage())); + } + } + else + { + logger.error("There is no valid Job Data available to be marshaled to a XML."); + } + + return false; + } + + /*---------------------*/ + /*-- Private Methods --*/ + /*---------------------*/ + + /* + * Unmarshal the Job XML to a proper Job. + * + * @param jobXML The JobXML to unmarshal to Job Object. + * + * @return The Job Object. Can be null if XML is null or empty or if the XML is not a JobType. In both cases an error is logged. + * + * @throws MarshalException Invalid data. Could not marshal. Error logged already. + */ + private JobType unmarschalJobXML(String jobXML) + { + if (jobXML == null) + { + logger.error("Job XML is null. Cannot unmrshal => Return null."); + isXMLValid = false; + return null; + } + InfraUnmarshalFactory unmarshaller = new InfraUnmarshalFactory(); + try + { + return (JobType)unmarshaller.unmarshalFromXML(jobXML, JobType.class); + } + catch (UnmarshalException ex) // cannot happen here + { + logger.error("Invalid Job XML. Cannot unmrshal => Return null.\nOffending XML:\n"+jobXML); + isXMLValid = false; + return null; + } + catch (UnsupportedMediaTypeExcpetion e) // should not happen + { + isXMLValid = false; + return null; + } + } + + /** + * Marshal a Job Object to XML. + * + * @param job The Job Object to marshal to XML. + * + * @return The final XML string. + * + * @throws MarshalException Invalid data. Could not marshal. Error logged already. + */ + private String createJobXML(JobType job) throws MarshalException + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + try + { + return marshaller.marshalToXML(job); + } + catch (UnsupportedMediaTypeExcpetion ex) // cannot happen here + { + return null; + } + } + + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ProviderEnvironment.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ProviderEnvironment.java index 38d40cec..1b38bb1f 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ProviderEnvironment.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/env/types/ProviderEnvironment.java @@ -19,8 +19,10 @@ package sif3.infra.common.env.types; import java.net.URI; +import java.util.HashSet; import sif3.common.CommonConstants.AuthenticationType; +import sif3.common.CommonConstants.JobState; import sif3.common.header.HeaderProperties; import sif3.common.header.HeaderValues.UpdateType; @@ -34,6 +36,7 @@ public class ProviderEnvironment extends EnvironmentInfo { private static final long serialVersionUID = 6469968451023182574L; + public static final String JOB_HOUSEKEEPING_CRON = "0 0 2 * * ?"; // once a day at 2am private boolean connected = false; private String templateXMLFileName = null; @@ -46,6 +49,14 @@ public class ProviderEnvironment extends EnvironmentInfo private boolean allowAuthOnURL = false; private HeaderProperties customResponseHeaders = new HeaderProperties(); + // Functional Service Properties + private boolean jobEnabled = false; // default + private HashSet jobEndStates = new HashSet(); + private int jobEventFrequency = 900; // default is 15 min = 900 seconds + private int jobEventKeepDays = 30; // default 30 days + private int jobKeepDays = 30; // default 30 days + private String jobHousekeepingCron = JOB_HOUSEKEEPING_CRON; + /** * Constructor * @@ -168,19 +179,6 @@ public void setAccessTokenAuthMethod(String accessTokenAuthMethod) this.accessTokenAuthMethod = accessTokenAuthMethod; } -// // authMethod for accessToken: Valid values are what is listed in AuthenticationUtils.AuthenticationMethod (case sensitive!!!) -// public void setAccessTokenAuthMethod(String accessTokenAuthMethod) -// { -// try -// { -// this.accessTokenAuthMethod = AuthenticationMethod.valueOf(accessTokenAuthMethod); -// } -// catch (Exception ex) -// { -// this.accessTokenAuthMethod = AuthenticationMethod.Bearer; -// } -// } - public boolean getAllowAuthOnURL() { return allowAuthOnURL; @@ -213,20 +211,80 @@ public void setCustomResponseHeaders(HeaderProperties customResponseHeaders) this.customResponseHeaders = customResponseHeaders; } } + + public HashSet getJobEndStates() + { + return jobEndStates; + } + + public void setJobEndStates(HashSet jobEndStates) + { + this.jobEndStates = jobEndStates; + } + + + public boolean isJobEnabled() + { + return jobEnabled; + } + + public void setJobEnabled(boolean jobEnabled) + { + this.jobEnabled = jobEnabled; + } + + public int getJobEventFrequency() + { + return jobEventFrequency; + } + + public void setJobEventFrequency(int jobEventFrequency) + { + this.jobEventFrequency = jobEventFrequency; + } + + public int getJobKeepDays() + { + return jobKeepDays; + } + + public void setJobKeepDays(int jobKeepDays) + { + this.jobKeepDays = jobKeepDays; + } + + public int getJobEventKeepDays() + { + return jobEventKeepDays; + } + + public void setJobEventKeepDays(int jobEventKeepDays) + { + this.jobEventKeepDays = jobEventKeepDays; + } + + public String getJobHousekeepingCron() + { + return jobHousekeepingCron; + } + + public void setJobHousekeepingCron(String jobHousekeepingCron) + { + this.jobHousekeepingCron = jobHousekeepingCron; + } - @Override + @Override public String toString() { - return "ProviderEnvironment [connected=" + connected - + ", templateXMLFileName=" + templateXMLFileName - + ", secureConnectorBaseURI=" + secureConnectorBaseURI - + ", connectorBaseURI=" + connectorBaseURI - + ", eventsSupported=" + eventsSupported - + ", defaultUpdateType=" + defaultUpdateType - + ", autoCreateEnvironment=" + autoCreateEnvironment - + ", accessTokenAuthMethod=" + accessTokenAuthMethod - + ", allowAuthOnURL=" + allowAuthOnURL - + ", customResponseHeaders=" + customResponseHeaders - + ", toString()=" + super.toString() + "]"; + return "ProviderEnvironment [connected=" + connected + ", templateXMLFileName=" + + templateXMLFileName + ", secureConnectorBaseURI=" + secureConnectorBaseURI + + ", connectorBaseURI=" + connectorBaseURI + ", eventsSupported=" + eventsSupported + + ", defaultUpdateType=" + defaultUpdateType + ", autoCreateEnvironment=" + + autoCreateEnvironment + ", accessTokenAuthMethod=" + accessTokenAuthMethod + + ", allowAuthOnURL=" + allowAuthOnURL + ", customResponseHeaders=" + + customResponseHeaders + ", jobEnabled=" + jobEnabled + ", jobEndStates=" + + jobEndStates + ", jobEventFrequency=" + jobEventFrequency + ", jobEventKeepDays=" + + jobEventKeepDays + ", jobKeepDays=" + jobKeepDays + ", jobHousekeepingCron=" + + jobHousekeepingCron + ", toString()=" + super.toString() + "]"; } } diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ConsumerJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ConsumerJobManager.java new file mode 100644 index 00000000..30af2a37 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ConsumerJobManager.java @@ -0,0 +1,28 @@ +/* + * ConsumerJobManager.java + * Created: 11 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.interfaces; + +/** + * This interface defines additional methods to the standard JobManager interface. These methods are applicable for the Consumer only. + * @author Joerg Huber + * + */ +public interface ConsumerJobManager extends JobManager +{ + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/EnvironmentManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/EnvironmentManager.java index 80788aa7..e2f54edf 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/EnvironmentManager.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/EnvironmentManager.java @@ -2,7 +2,7 @@ * EnvironmentManager.java * Created: 15/03/2014 * - * Copyright 2014 Systemic Pty Ltd + * Copyright 2014-2018 Systemic Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,12 @@ import java.util.Date; +import au.com.systemic.framework.utils.AdvancedProperties; +import sif3.common.CommonConstants.AdapterType; import sif3.common.persist.model.SIF3Session; import sif3.infra.common.env.types.EnvironmentInfo; import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; -import au.com.systemic.framework.utils.AdvancedProperties; +import sif3.infra.common.model.JobType; /** * This interface defines all methods required by this framework in relation to any environment managers (consumer, provider, direct, @@ -53,6 +55,13 @@ public interface EnvironmentManager */ public AdvancedProperties getServiceProperties(); + /** + * returns the adapter type for this environment manager. + * + * @return See Desc. + */ + public AdapterType getAdapterType(); + /** * This method should return an active, existing and already loaded, session from the session store for the given sessionToken. * If there is no session in the session store for the given sessionToken then null must be returned. This method MUST NOT attempt @@ -92,6 +101,26 @@ public interface EnvironmentManager */ public boolean updateSessionSecurityInfo(String sessionToken, String securityToken, Date securityExpiryDate); + /** + * This method will attempt to load the job template from the underlying job template store. + * If there is no job template for the given urlName or an error occurs accessing the store then null is returned. + * + * @param urlName The unique name of the job template as given in the job URL for which the template shall be + * retrieved. A job URL is generally of the form .../job//... The uniqueJobName is what the + * URL name refers to and is the same value as in the SIF3_JOB_TEMPLATE table in the column JOB_URL_NAME. + * + * @return See desc. + */ + public JobType getJobTemplate(String urlName); + + /** + * Returns the Job Manager for this environment manager. The Job Manager abstracts a number of useful functions relating to + * functional services and how they are managed in the data stores and event management. + * + * @return See desc. + */ + public JobManager getJobManager(); + /** * This method must return the environment type of the given environment manager. Note for providers this must always be set. For * consumer this is mostly irrelevant as they behave the same independent if it is a brokered or direct environment. For consumers diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/JobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/JobManager.java new file mode 100644 index 00000000..1521fae7 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/JobManager.java @@ -0,0 +1,174 @@ +/* + * JobManager.java + * Created: 13/02/2018 + * + * Copyright 2014-2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package sif3.infra.common.interfaces; + +import sif3.common.CommonConstants.AdapterType; +import sif3.common.exception.PersistenceException; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.common.persist.model.JobTemplate; +import sif3.common.persist.model.SIF3Job; +import sif3.infra.common.env.types.EnvironmentInfo; +import sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType; +import sif3.infra.common.env.types.ExtendedJobInfo; +import sif3.infra.common.model.JobCollectionType; +import sif3.infra.common.model.JobType; + +/** + * This interface defines all methods required by this framework in relation to any jobs (functional services) for consumer, provider, direct, + * brokered etc. These are the minimum functions. Some more specific interfaces for some job managers may extend this interface to define some + * more detailed functionality. + * + * @author Joerg Huber + * + */ +public interface JobManager +{ + /** + * Returns the environment information (configuration) that goes with this job manager. The environment information is just the + * underlying information that relates to environment configuration file related to the specific adapter type. If there is no + * initialised environment configuration for this environment then null shall be returned. + * + * @return See desc. + */ + public EnvironmentInfo getEnvironmentInfo(); + + /** + * returns the adapter type for this job manager. + * + * @return See Desc. + */ + public AdapterType getAdapterType(); + + /** + * This method must return the environment type of the given job manager. Note for providers this must always be set. For + * consumer this is mostly irrelevant as they behave the same independent if it is a brokered or direct environment. For consumers + * this method my return null. Possible values are DIRECT or BROKERED. + * + * @return See desc. + */ + public EnvironmentType getEnvironmentType(); + + /** + * This method will attempt to load the job template info from the underlying job template info store. Note that + * this method won't load the actual template (XML) rather it returns some meta information around the Job template. + * If there is no entry in job template store store for the given urlName. If no such template info exists or an error + * occurs accessing the store then null is returned. + * + * @param urlName The unique name of the job template as given in the job URL for which the template meta data shall be + * retrieved. A job URL is generally of the form .../job//... The uniqueJobName is what the + * URL name refers to and is the same value as in the SIF3_JOB_TEMPLATE table in the column JOB_URL_NAME. + * + * @return See desc. + */ + public JobTemplate getJobTemplateInfo(String urlName); + + /** + * This method shall return the job given by its ID (refID). If no such job exists then null is returned. + * + * @param jobID The ID of the job to be returned. If null or empty then null is returned. + * + * @return See desc. + * + * @throws PersistenceException If the underlying data store cannot be accessed. + */ + public SIF3Job getJob(String jobID) throws PersistenceException; + + /** + * This method will persist the job to the appropriate tables. For a provider it may also store an entry in the job event table + * if the provider is a brokered provider and has event support. + * + * @param newJob The job to be persisted. + * @param serviceName Cannot be null. It identifies if functional service name as defined by the service URL. + * @param zone The zone for which the job is applicable. + * @param context The context for which the job is applicable. + * @param environmentID The environment for which the job is applicable. + * @param fingerprint The fingerprint of the Adapter that requested the job creation. + * @param consumerRequested Indicating if the request is caused by consumer. + * + * @throws PersistenceException Some data could not be persisted. An error log entry is performed and the message of the exceptions + * holds some info. + */ + public void saveNewJob(JobType newJob, String serviceName, SIFZone zone, SIFContext context, String environmentID, String fingerprint, boolean consumerRequested) throws PersistenceException; + + /** + * This method will attempt to remove the job for the given jobID (job RefID) from the SIF3_JOB table. If it exists it will also make a + * "DELETE" entry in the SIF3_JOB_EVENT table if events are supported. If event support is not enabled then no entry will be performed + * to the SIF3_JOB_EVENT table. + * + * @param jobID The refID of the job to be removed. + * @param consumerRequested Indicating if the request is caused by consumer. + * + * @return TRUE: Job is removed and optional an entry is made in the event table. FALSE: Job did not exist. No changes are performed. + * + * @throws PersistenceException Some data could not be persisted/removed. An error log entry is performed and the message of the exceptions + * holds some info. + */ + public boolean removeJob(String jobID, boolean consumerRequested) throws PersistenceException; + + /** + * This method will attempt to get the job for the given jobID (job RefID) from the SIF3_JOB table. If it exists it will return the job + * object. If it doesn't exist then null is returned. + * + * @param jobID The refID of the job to be retrieved. + * + * @return Job if it exists otherwise null is returned. + * + * @throws PersistenceException Some data could not be accessed. An error log entry is performed and the message of the exceptions + * holds some info. + */ + public JobType retrieveJob(String jobID) throws PersistenceException; + + /** + * This method retrieves all jobs for the given adapter identified by the fingerprint, zone & context. If no fingerprint is given + * it gets all jobs for the given zone & context. The jobs list returned is also limited to the values given in the pagingInfo parameter. + * + * @param serviceName Can be null. It identifies if functional service name as defined by the service URL. + * @param fingerprint Can be null. It identifies if jobs for a specific adapter shall be returned or any job. + * @param zone The zone for which the jobs shall be returned. + * @param context The context for which the jobs shall be returned. + * @param pagingInfo Limiting the returned job list to the given 'window'. + * + * @return A list of jobs that match the criteria given by the input parameters. It can be an empty list if no jobs match the + * criteria. Should never be null. + * + * @throws PersistenceException Some data could not be accessed. An error log entry is performed and the message of the exceptions + * holds some info. + */ + public JobCollectionType retrieveJobs(String serviceName, String fingerprint, SIFZone zone, SIFContext context, PagingInfo pagingInfo) throws PersistenceException; + + + /** + * This method attempts to get the ExtendedJobInfo for the given Job. There are many places in the Functional Service + * implementation where not only the DB representation but also the XML representation of the job is required. This method allows + * to have one object that hold bot representations. It may also help avoiding too many DB accesses and conversions between + * String and JAXB data. + * + * @param jobID The ID of the Job for which the extended object shall be retrieved. If job does not exist then null is returned. + * + * @return See Desc. + * + * @throws PersistenceException Some data could not be accessed. An error log entry is performed and the message of the exceptions + * holds some info. + */ + public ExtendedJobInfo getJobInfo(String jobID) throws PersistenceException; + + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ProviderJobManager.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ProviderJobManager.java new file mode 100644 index 00000000..b5c60f11 --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/interfaces/ProviderJobManager.java @@ -0,0 +1,110 @@ +/* + * ProviderJobManager.java + * Created: 11 Jun 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.interfaces; + +import java.util.Date; + +import sif3.common.CommonConstants.JobState; +import sif3.common.CommonConstants.PhaseState; +import sif3.common.exception.PersistenceException; +import sif3.common.model.PagingInfo; +import sif3.common.model.SIFContext; +import sif3.common.model.SIFZone; +import sif3.infra.common.env.types.ExtendedJobInfo; +import sif3.infra.common.model.JobCollectionType; + +/** + * This interface defines additional methods to the standard JobManager interface. These methods are applicable for the Provider only. + * + * @author Joerg Huber + * + */ +public interface ProviderJobManager extends JobManager +{ + /** + * This method updates the overall Job state for the given job. It is intended to be used by the provider only. If events + * are turned on it will create the appropriate event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param newState The new state of the job. If null then no action is taken. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updateJobState(String jobID, JobState newState) throws PersistenceException; + + /** + * This method updates the state for the given phase of the given job. If the job or phase doesn't exist then no action + * is taken. If events are turned on it will create the appropriate event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param phaseName The phase for which the state shall be updated. If phase doesn't exist then no action is taken. + * @param newState The new state of the job. If null then no action is taken. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updatePhaseState(String jobID, String phaseName, PhaseState newState) throws PersistenceException; + + /** + * This method updates the overall Job status as well as the status of the given phase. This is a convenience method, so that + * a provider can update a phase state which in turn may also need an update to the overall job state. Using this method + * is more efficient then using two separate methods to update job and a phase state separately. It also reduces the number + * of events that will be created in case events are turned on. This method will only create on final event. + * + * @param jobID The ID of the Job to be updated. If Job does not exist then no action is taken. + * @param newJobState The new state of the job. If null then the state will not be updated. + * @param phaseName The phase for which the state shall be updated. If phase doesn't exist then no action is taken. + * @param newPhaseState The new state of the given phase. If null then the state will not be updated. + * + * @throws PersistenceException Job can not be accessed or updated due to a DB access issue. + */ + public void updateJobStateAndPhaseState(String jobID, JobState newJobState, String phaseName, PhaseState newPhaseState) throws PersistenceException; + + /** + * This method persist the jobInfo.dbJob to the data store. If events are enabled it will also add an appropriate event to the event + * table. Internally this method will marshal the latest jobInfo.xmlJob into the appropriate dbJob.jobXML property before it is + * persisted. Other properties like lastUpdated will be set with an appropriate timestamp as well. The jobInfo parameter will hold + * all the latest values after this call. + * + * @param jobInfo The job data that needs to be persisted. If null then no action is taken. + * @param consumerRequested Indicating if the update request is caused by consumer. + * + * @throws PersistenceException Job can not be updated due to a DB access issue. + */ + public void updateJob(ExtendedJobInfo jobInfo, boolean consumerRequested) throws PersistenceException; + + /** + * This method returns all Jobs that have had any changes applied to them on or after the 'changesSince' date. The number + * of jobs returned in the collection is indicated by the 'pagingInfo' parameter. Note because this shall behave as the + * "ChangesSince" as specified by the SIF 3.2.x specification jobs that have been removed are returned as and "empty" job + * with just the 'id' property set. + * + * @param changesSince Must not be null. + * @param serviceName Can be null. It identifies if functional service name as defined by the service URL. + * @param fingerprint Can be null. It identifies if jobs for a specific adapter shall be returned or any job. + * @param zone The zone for which the jobs shall be returned. + * @param context The context for which the jobs shall be returned. + * @param pagingInfo Limiting the returned job list to the given 'window'. + * + * @return A collection of job as described. Is never null but can be empty. + * + * @throws IllegalArgumentException If the changesSince or adapterType is null. + * @throws PersistenceException Could not access underlying data store. + */ + public JobCollectionType retrieveJobChangesSince(Date changesSince, String serviceName, String fingerprint, SIFZone zone, SIFContext context, PagingInfo pagingInfo) throws PersistenceException; + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/utils/SIFSessionUtils.java b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/utils/SIFSessionUtils.java index 9fb08559..e1ab5974 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/utils/SIFSessionUtils.java +++ b/SIF3InfraREST/sif3InfraCommon/src/main/java/sif3/infra/common/utils/SIFSessionUtils.java @@ -92,7 +92,7 @@ public static synchronized void loadServiceInfoForSession(SIF3Session sif3Sessio } catch (Exception ex) // log error and assume it is OBJECT { - logger.warn("The service '"+service.getName()+"' for environment '"+sif3Session.getEnvironmentName()+"' has an invalid 'type' of '"+service.getType().trim()+"' set. Valid values are: OBJECT, FUNCTION, UTILITY, SERVICEPATH and XQUERYTEMPLATE."); + logger.warn("The service '"+service.getName()+"' for environment '"+sif3Session.getEnvironmentName()+"' has an invalid 'type' of '"+service.getType().trim()+"' set. Valid values are: OBJECT, FUNCTIONAL, UTILITY, SERVICEPATH and XQUERYTEMPLATE."); serviceType = ServiceType.OBJECT; } } diff --git a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestBase.java b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestBase.java new file mode 100644 index 00000000..f9399d4a --- /dev/null +++ b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestBase.java @@ -0,0 +1,65 @@ +/* + * TestBase.java + * Created: 9 Jan 2018 + * + * Copyright 2018 Systemic Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package sif3.infra.common.test.env; + +import sif3.common.exception.MarshalException; +import sif3.common.exception.UnsupportedMediaTypeExcpetion; +import sif3.common.persist.common.HibernateUtil; +import sif3.infra.common.conversion.InfraMarshalFactory; + +/** + * @author Joerg Huber + * + */ +public class TestBase +{ + public TestBase() + { + HibernateUtil.initialise(null); + } + + public void shutdown() + { + HibernateUtil.shutdown(); + } + + protected void printInfraObjectAsXML(Object infraObj) + { + if (infraObj == null) + { + System.out.println("Null object provided!"); + } + else + { + try + { + InfraMarshalFactory marshaller = new InfraMarshalFactory(); + System.out.println(infraObj.getClass().getSimpleName()+" as XML:\n" + marshaller.marshalToXML(infraObj)); + } + catch (MarshalException ex) + { + ex.printStackTrace(); + } + catch (UnsupportedMediaTypeExcpetion ex) + { + ex.printStackTrace(); + } + } + } + +} diff --git a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestConsumerEnvStoreOps.java b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestConsumerEnvStoreOps.java index d7db7e81..fa54fb35 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestConsumerEnvStoreOps.java +++ b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestConsumerEnvStoreOps.java @@ -25,13 +25,14 @@ import sif3.infra.common.conversion.InfraMarshalFactory; import sif3.infra.common.env.ops.ConsumerEnvironmentStoreOperations; import sif3.infra.common.model.EnvironmentType; +import sif3.infra.common.model.JobType; /** * @author Joerg Huber * */ -public class TestConsumerEnvStoreOps +public class TestConsumerEnvStoreOps extends TestBase { private static final AdapterType CONSUMER = AdapterType.CONSUMER; private static final sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType DIRECT = sif3.infra.common.env.types.EnvironmentInfo.EnvironmentType.DIRECT; @@ -39,6 +40,7 @@ public class TestConsumerEnvStoreOps private ConsumerEnvironmentStoreOperations envOps = null; private static final String TEMPLATE_FILE_NAME="devLocal.xml"; + private static final String JOB_TEMPLATE_FILE_NAME="rolloverStudentJob.xml"; private static final String SESSION_TOKEN="12341234-5678-4123-1234-123412345678"; //private static final String SESSION_TOKEN="e88da655-9ca6-41c3-aa21-ec467ed84437"; //private static final String SESSION_TOKEN="98154bc3-d4b3-427d-bf40-58ed9705f6de"; @@ -51,39 +53,23 @@ public class TestConsumerEnvStoreOps public TestConsumerEnvStoreOps() { - HibernateUtil.initialise(null); +// HibernateUtil.initialise(null); + super(); envOps = new ConsumerEnvironmentStoreOperations(SERVICE_NAME); } - private void printEnvironment(EnvironmentType env) - { - if (env == null) - { - System.out.println("Environment is null!"); - } - else - { - try - { - InfraMarshalFactory marshaller = new InfraMarshalFactory(); - System.out.println("Environment as XML:\n"+marshaller.marshalToXML(env)); - } - catch (MarshalException ex) - { - ex.printStackTrace(); - } - catch (UnsupportedMediaTypeExcpetion ex) - { - ex.printStackTrace(); - } - } - } private void testExistTemplateEnvironment() { System.out.println("Template XML for "+TEMPLATE_FILE_NAME+" exists: "+envOps.existEnvironmentTemplate(TEMPLATE_FILE_NAME, CONSUMER, DIRECT)); } + private void testExistJobTemplate() + { + System.out.println("Job Template XML for "+JOB_TEMPLATE_FILE_NAME+" exists: "+envOps.existJobTemplate(JOB_TEMPLATE_FILE_NAME, CONSUMER, DIRECT)); + } + + // private void testExistWorkstoreEnvBySessionTK() throws IllegalArgumentException, PersistenceException // { // System.out.println("Workstore XML for Session Token "+SESSION_TOKEN+" exists: "+envOps.existEnvInWorkstoreBySessionToken(SESSION_TOKEN)); @@ -91,9 +77,15 @@ private void testExistTemplateEnvironment() private void testLoadTemplateEnvironment() { - printEnvironment(envOps.loadTemplateEnvironmentData(TEMPLATE_FILE_NAME, CONSUMER, DIRECT)); + printInfraObjectAsXML(envOps.loadTemplateEnvironmentData(TEMPLATE_FILE_NAME, CONSUMER, DIRECT)); } + private void testLoadJobTemplate() + { + printInfraObjectAsXML(envOps.loadJobTemplateData(JOB_TEMPLATE_FILE_NAME, CONSUMER, DIRECT)); + } + + // private void testLoadWorkstoreEnvBySessionTK() // { // printEnvironment(envOps.loadWorkstoreEnvBySessionToken(SESSION_TOKEN)); @@ -131,8 +123,10 @@ public static void main(String[] args) System.out.println("Start Testing ConsumerEnvironmentStoreOperations..."); // tester.testExistTemplateEnvironment(); +// tester.testExistJobTemplate(); // tester.testExistWorkstoreEnvBySessionTK(); - tester.testLoadTemplateEnvironment(); +// tester.testLoadTemplateEnvironment(); + tester.testLoadJobTemplate(); // tester.testLoadWorkstoreEnvBySessionTK(); // tester.testStoreEnvironmentToWorkstore(); // tester.testRemoveEnvFromWorkstoreBySessionTK(); @@ -141,6 +135,10 @@ public static void main(String[] args) { ex.printStackTrace(); } + finally + { + tester.shutdown(); + } System.out.println("End Testing ConsumerEnvironmentStoreOperations."); } } diff --git a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestDirectProviderEnvStoreOps.java b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestDirectProviderEnvStoreOps.java index 426ce92a..fbb264ad 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestDirectProviderEnvStoreOps.java +++ b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestDirectProviderEnvStoreOps.java @@ -37,7 +37,7 @@ * @author Joerg Huber * */ -public class TestDirectProviderEnvStoreOps +public class TestDirectProviderEnvStoreOps extends TestBase { private static final long MINUTE = 1000*60; private static final AdapterType PROVIDER = AdapterType.ENVIRONMENT_PROVIDER; @@ -48,6 +48,7 @@ public class TestDirectProviderEnvStoreOps // private static final String TEMPLATE_FILE_NAME="systemicDemoBrokerResponse.xml"; private static final String TEMPLATE_FILE_NAME="devLocal.xml"; + private static final String JOB_TEMPLATE_FILE_NAME="rolloverStudentJob.xml"; private static final String SERVICE_NAME="StudentProvider"; private static ObjectFactory objFactory = new ObjectFactory(); @@ -63,34 +64,11 @@ public class TestDirectProviderEnvStoreOps public TestDirectProviderEnvStoreOps() { - HibernateUtil.initialise(null); +// HibernateUtil.initialise(null); + super(); envOps = new DirectProviderEnvStoreOps(SERVICE_NAME); } - private void printEnvironment(EnvironmentType env) - { - if (env == null) - { - System.out.println("Environment is null!"); - } - else - { - try - { - InfraMarshalFactory marshaller = new InfraMarshalFactory(); - System.out.println("Environment as XML:\n"+marshaller.marshalToXML(env)); - } - catch (MarshalException ex) - { - ex.printStackTrace(); - } - catch (UnsupportedMediaTypeExcpetion ex) - { - ex.printStackTrace(); - } - } - } - private EnvironmentType getInputEnvironment() { EnvironmentType env = objFactory.createEnvironmentType(); @@ -121,11 +99,22 @@ private void testExistEnvironmentTemplate() System.out.println("Does "+TEMPLATE_FILE_NAME+" exist in template directory: "+envOps.existEnvironmentTemplate(TEMPLATE_FILE_NAME)); } - private void testLoadEnvironmentTemplate() + private void testExistJobTemplate() + { + System.out.println("Job Template XML for "+JOB_TEMPLATE_FILE_NAME+" exists: "+envOps.existJobTemplate(JOB_TEMPLATE_FILE_NAME)); + } + + private void testLoadEnvironmentTemplate() { - printEnvironment(envOps.loadEnvironmentFromTemplate(TEMPLATE_FILE_NAME)); + printInfraObjectAsXML(envOps.loadEnvironmentFromTemplate(TEMPLATE_FILE_NAME)); } + + private void testLoadJobTemplate() + { + printInfraObjectAsXML(envOps.loadJobFromTemplate(JOB_TEMPLATE_FILE_NAME)); + } + private void testExistEnvInWorkstore() { try @@ -142,7 +131,7 @@ private void testLoadEnvironmentFromWorkstore() { try { - printEnvironment(envOps.loadEnvironmentFromWorkstore(new EnvironmentKey(SOLUTION_NAME, APPLICATION_KEY, null, null), null, USE_HTTPS)); + printInfraObjectAsXML(envOps.loadEnvironmentFromWorkstore(new EnvironmentKey(SOLUTION_NAME, APPLICATION_KEY, null, null), null, USE_HTTPS)); } catch (Exception ex) { @@ -164,7 +153,7 @@ private void testCreateEnvironment() { // EnvironmentType inputEnv = envOps.loadEnvironmentFromTemplate(TEMPLATE_FILE_NAME); // use this if BROKERED EnvironmentType inputEnv = getInputEnvironment(); // use this for DIRECT - printEnvironment(envOps.createEnvironmentAndSession(inputEnv, null, USE_HTTPS)); + printInfraObjectAsXML(envOps.createEnvironmentAndSession(inputEnv, null, USE_HTTPS)); } private void testCreateSession() @@ -205,15 +194,19 @@ public static void main(String[] args) System.out.println("Start Testing ProviderEnvironmentStoreOperations..."); // tester.testExistEnvironmentTemplate(); +// tester.testExistJobTemplate(); // tester.testLoadEnvironmentTemplate(); + tester.testLoadJobTemplate(); // tester.testExistEnvInWorkstore(); // tester.testLoadEnvironmentFromWorkstore(); // tester.testCreateEnvironment(); - tester.testCreateSession(); +// tester.testCreateSession(); // tester.testRemoveEnvFromWorkstoreByEnvID(); // tester.testRemoveEnvFromWorkstoreBySessionToken(); // tester.testUpdateSessionSecurityInfo(); + tester.shutdown(); + System.out.println("End Testing ProviderEnvironmentStoreOperations."); } } diff --git a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestEnvStore.java b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestEnvStore.java deleted file mode 100644 index a2a0b251..00000000 --- a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestEnvStore.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * TestEnvStore.java - * Created: 27/08/2013 - * - * Copyright 2013 Systemic Pty Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. - * See the License for the specific language governing permissions and limitations under the License. - */ - -package sif3.infra.common.test.env; - -import sif3.infra.common.env.types.AdapterEnvironmentStore; - - -/** - * @author Joerg Huber - * - */ -public class TestEnvStore -{ - private AdapterEnvironmentStore testGetEnvironmentStore(String propFileName) - { - AdapterEnvironmentStore envStore = new AdapterEnvironmentStore(propFileName); - System.out.println("Environment Store for "+propFileName+":\n"+envStore); - return envStore; - } - - - - public static void main(String[] args) - { - TestEnvStore tester = new TestEnvStore(); - - System.out.println("Start Testing EnvironmentStore..."); - -// tester.testGetEnvironmentStore("StudentProvider"); - tester.testGetEnvironmentStore("StudentConsumer"); - - System.out.println("End Testing EnvironmentStore."); - } -} diff --git a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestProviderEnvMgr.java b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestProviderEnvMgr.java index 856e296c..114ef57c 100644 --- a/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestProviderEnvMgr.java +++ b/SIF3InfraREST/sif3InfraCommon/src/test/java/sif3/infra/common/test/env/TestProviderEnvMgr.java @@ -2,12 +2,13 @@ import sif3.infra.common.env.mgr.DirectProviderEnvironmentManager; -public class TestProviderEnvMgr +public class TestProviderEnvMgr extends TestBase { private DirectProviderEnvironmentManager envMgr = null; public TestProviderEnvMgr() { + super(); DirectProviderEnvironmentManager.initialse("StudentProvider"); envMgr = DirectProviderEnvironmentManager.getInstance(); } @@ -17,15 +18,30 @@ private void printEnvManagerData() System.out.println("Provider Environment Manager Data:\n" + envMgr); } - public static void main(String[] args) + private void printJobData(String jobURLName) + { + printInfraObjectAsXML(envMgr.getJobTemplate(jobURLName)); + } + + private void testJobManager() + { + System.out.println("Job Manager - Adapter Type: "+envMgr.getJobManager().getAdapterType()); + System.out.println("Job Manager - Job Template Info: "+envMgr.getJobManager().getJobTemplateInfo("RolloverStudents")); + } + + public static void main(String[] args) { // String envName = "devLocal"; TestProviderEnvMgr tester = new TestProviderEnvMgr(); System.out.println("Start Testing DirectProviderEnvironmentManager..."); - tester.printEnvManagerData(); +// tester.printEnvManagerData(); +// tester.printJobData("RolloverStudents"); + tester.testJobManager(); System.out.println("End Testing DirectProviderEnvironmentManager."); + + tester.shutdown(); } } diff --git a/SIF3InfraREST/sif3InfraModel/pom.xml b/SIF3InfraREST/sif3InfraModel/pom.xml index 0f6c196e..89a28cbb 100644 --- a/SIF3InfraREST/sif3InfraModel/pom.xml +++ b/SIF3InfraREST/sif3InfraModel/pom.xml @@ -7,7 +7,7 @@ sif3.framework sif3-framework - 0.12.0-beta + 0.13.0-beta diff --git a/SIF3InfraREST/sif3InfraModel/src/test/java/sif3/infra/common/test/conversion/.gitignore b/SIF3InfraREST/sif3InfraModel/src/test/java/sif3/infra/common/test/conversion/.gitignore new file mode 100644 index 00000000..b15b3d53 --- /dev/null +++ b/SIF3InfraREST/sif3InfraModel/src/test/java/sif3/infra/common/test/conversion/.gitignore @@ -0,0 +1 @@ +/TestJobObject.java diff --git a/SIF3InfraREST/src/test/resources/config/consumers/HITSStudentConsumer.properties b/SIF3InfraREST/src/test/resources/config/consumers/HITSStudentConsumer.properties index 0805309a..2181207b 100644 --- a/SIF3InfraREST/src/test/resources/config/consumers/HITSStudentConsumer.properties +++ b/SIF3InfraREST/src/test/resources/config/consumers/HITSStudentConsumer.properties @@ -66,17 +66,17 @@ adapter.noCertificateCheck=false env.events.supported=false env.xml.file.name=HITS.xml -env.application.key=9b19d71b03f84f16aa71f04034c1f714 +env.application.key=f47a7b923efc49199222fd281419f279 #env.application.key=BearerTest # This allows to set/override the userToken and/or instanceId element in the environment XML. This can be used if # multiple instances of this adapter shall be run in the same environment configuration. In such case each instance # can have a different userToken to distinguish the instances. If it is empty then this property won't be used and # the value in the environment XML template is used. -env.userToken=9b19d71b03f84f16aa71f04034c1f714 +env.userToken=f47a7b923efc49199222fd281419f279 env.instanceID= -env.pwd=9b19d71b03f84f16aa71f04034c1f714 +env.pwd=f47a7b923efc49199222fd281419f279 # mediaType can be XML or JSON (case in-sensitive). Currently only XML is supported by Framework env.mediaType=XML @@ -105,13 +105,13 @@ env.baseURI=http://hits.nsip.edu.au/SIF3InfraREST/hits/environments/environment # This property indicates if there is no existing environment in local session store then try to use an # existing from Environment Provider rather than create a new one with the environment provider. Default is false. -env.use.existing=false +env.use.existing=true # The session token to use with a pre-existing environment -env.existing.sessionToken=107c2bac-172f-4699-956c-ebfcaff901c2 +env.existing.sessionToken=9fd1d3cc-14c9-46e1-839b-d58dd64ae2b9 # The full URI of the pre-existing environment. -env.existing.environmentURI=http://hits.nsip.edu.au/SIF3InfraREST/hits/environments/d9b37329-54df-4e2a-8964-b8b282fb08c4 +env.existing.environmentURI=http://hits.nsip.edu.au/SIF3InfraREST/hits/environments/69d7c7db-13ca-43d0-a184-ffdc5801a7a3 # # This property indicates if a 'conflict' or HTTP Status of 409 for a create environment shall be a treated as an error (true) or if it shall diff --git a/SIF3InfraREST/src/test/resources/config/consumers/StudentConsumer.properties b/SIF3InfraREST/src/test/resources/config/consumers/StudentConsumer.properties index a4be22f1..040abf07 100644 --- a/SIF3InfraREST/src/test/resources/config/consumers/StudentConsumer.properties +++ b/SIF3InfraREST/src/test/resources/config/consumers/StudentConsumer.properties @@ -117,7 +117,6 @@ env.existing.environmentURI=http://localhost:9080/SIF3InfraREST/sif3/environment # env.create.conflictIsError=false - #---------------------------------# # Event related properties #---------------------------------# @@ -233,7 +232,9 @@ consumer.basePackageName=systemic.sif3.demo.rest.consumer #Name of all Consumer Classes that make up this Consumer. This is a comma separated list #consumer.classes=StudentPersonalConsumer,SchoolInfoConsumer -consumer.classes=StudentPersonalConsumer +#consumer.classes=StudentPersonalConsumer, functional.RolloverStudentConsumer +consumer.classes=functional.RolloverStudentConsumer +#consumer.classes=StudentPersonalConsumer #consumer.classes=CSVStudentConsumer #consumer.classes=StudentPersonalConsumer, StudentDailyAttendanceConsumer diff --git a/SIF3InfraREST/src/test/resources/config/environments/consumer/template/job/rolloverStudentJob.xml b/SIF3InfraREST/src/test/resources/config/environments/consumer/template/job/rolloverStudentJob.xml new file mode 100644 index 00000000..e76b0cbc --- /dev/null +++ b/SIF3InfraREST/src/test/resources/config/environments/consumer/template/job/rolloverStudentJob.xml @@ -0,0 +1,29 @@ + + rolloverStudent + Rollover Student from year X to year Y + + + + + future + user1 + vendora + 3pi + 2018 + + + + \ No newline at end of file diff --git a/SIF3InfraREST/src/test/resources/config/environments/provider/template/direct/devLocal.xml b/SIF3InfraREST/src/test/resources/config/environments/provider/template/direct/devLocal.xml index 4f1efae6..2e098f55 100644 --- a/SIF3InfraREST/src/test/resources/config/environments/provider/template/direct/devLocal.xml +++ b/SIF3InfraREST/src/test/resources/config/environments/provider/template/direct/devLocal.xml @@ -85,6 +85,17 @@ REJECTED + + + APPROVED + APPROVED + APPROVED + APPROVED + REJECTED + APPROVED + REJECTED + + APPROVED diff --git a/SIF3InfraREST/src/test/resources/config/environments/provider/template/job/rolloverStudentJob.xml b/SIF3InfraREST/src/test/resources/config/environments/provider/template/job/rolloverStudentJob.xml new file mode 100644 index 00000000..a084b496 --- /dev/null +++ b/SIF3InfraREST/src/test/resources/config/environments/provider/template/job/rolloverStudentJob.xml @@ -0,0 +1,53 @@ + + rolloverStudent + Rollover Student from year X to year Y + NOTSTARTED + Not Started + + + P30D + + + oldYearEnrolment + + + NOTSTARTED + + + + true + + APPROVED + + + APPROVED + APPROVED + + + + newYearEnrolment + + + NOTSTARTED + + + + true + + APPROVED + + + APPROVED + APPROVED + + + + \ No newline at end of file diff --git a/SIF3InfraREST/src/test/resources/config/log4j.properties b/SIF3InfraREST/src/test/resources/config/log4j.properties index 610d6992..4c3634f1 100644 --- a/SIF3InfraREST/src/test/resources/config/log4j.properties +++ b/SIF3InfraREST/src/test/resources/config/log4j.properties @@ -27,16 +27,14 @@ log4j.logger.sif3.infra=DEBUG log4j.logger.sif3.infra.rest.client.ClientConfigMgr=DEBUG log4j.logger.sif3.infra.rest.client.MessageClient=DEBUG log4j.logger.sif3.infra.rest.client.EventClient=DEBUG +log4j.logger.sif3.infra.rest.queue.LocalConsumerQueue=INFO +log4j.logger.sif3.infra.rest.queue.LocalMessageConsumer=INFO log4j.logger.sif3.infra.rest.queue.RemoteMessageQueueReader=INFO #log4j.logger.sif3.infra.rest.resource.BaseResource=DEBUG log4j.logger.sif3.infra.rest.resource.DataModelResource=DEBUG log4j.logger.sif3.common.utils.JAXBUtils=DEBUG -## JSF Stuff -#log4j.logger.javax.faces = ERROR -#log4j.logger.org.ajax4jsf=ERROR -#log4j.logger.catalog = ERROR ## Debug for connection pool @@ -56,7 +54,7 @@ log4j.logger.sif3.common.utils.JAXBUtils=DEBUG #log4j.logger.org.hibernate.pretty=ERROR #log4j.logger.org.hibernate.event=ERROR #log4j.logger.org.hibernate.engine=DEBUG -log4j.logger.org.hibernate=ERROR +log4j.logger.org.hibernate=INFO #log4j.logger.org.hibernate.cfg.HbmBinder=INFO #log4j.logger.org.hibernate.SQL=INFO @@ -67,7 +65,7 @@ log4j.logger.org.hibernate=ERROR ## Debug for C3P0 connection pool -log4j.logger.com.mchange.v2=ERROR +log4j.logger.com.mchange.v2=INFO # JDBC Stuff log4j.logger.au.com.systemic.framework.dao.sql=ERROR @@ -76,4 +74,7 @@ log4j.logger.au.com.systemic.framework.dao.sql=ERROR #log4j.logger.org.apache.commons=ERROR #log4j.logger.org.apache.commons.httpclient=INFO #log4j.logger.httpclient.wire=INFO + +#Quartz Stuff +log4j.logger.org.quartz=ERROR \ No newline at end of file diff --git a/SIF3InfraREST/src/test/resources/config/providers/StudentProvider.properties b/SIF3InfraREST/src/test/resources/config/providers/StudentProvider.properties index 587932d0..9209e701 100644 --- a/SIF3InfraREST/src/test/resources/config/providers/StudentProvider.properties +++ b/SIF3InfraREST/src/test/resources/config/providers/StudentProvider.properties @@ -13,6 +13,9 @@ adapter.id=StudentProvider # Turn on (true) or off (false) ACL checks on provider. Default = true adapter.checkACL=true +# External security service shall be used if Bearer Token is used. +#adapter.security.service= + # Fully qualified name of the audit service implementation class to be used by this adapter. The class listed here must implement Auditor interface. If no # class is listed then it is assumed that audit logging is not enabled. #adapter.audit.service=systemic.sif3.demo.audit.LogAuditor @@ -33,7 +36,6 @@ adapter.default.accessToken.authentication.method=SIF_HMACSHA256 # Default: false adapter.authTokenOnURL.allowed=true - # Optional generator ID. Can be used as an identifier of the provider. This value is provided as a HTTP header field # in each event that is sent to the broker. The broker will pass this HTTP header field on to the consumer as is. #adapter.generator.id=sis-nsw @@ -166,7 +168,7 @@ provider.basePackageName=systemic.sif3.demo.rest.provider #Name of all Provider Classes that make up this Provider. This is a comma separated list #provider.classes=StudentPersonalProvider,SchoolInfoProvider,DailyAttendanceProvider #provider.classes= -provider.classes=StudentPersonalProvider +provider.classes=StudentPersonalProvider, functional.RolloverStudentsProvider #provider.classes=CSVStudentProvider #provider.classes=StudentPersonalProvider,CSVStudentProvider @@ -208,6 +210,105 @@ event.frequency.SchoolInfoProvider=45 # event messages valid. I no value is provided then it will assume "FULL". Valid values are FULL and PARTIAL (not case-sensitive). event.default.updateType=FULL +#-------------------------------------------# +#-- Functional Service related Properties --# +#-------------------------------------------# + +# +# This property indicates if functional services are being used by this adapter. If it is set to false (default value) then no +# background processes will start (see job.housekeeping.cron property). If it is set to true then a number of housekeeping processes +# will be running from time to time. This includes eventing etc. It is important to turn this property on (true) if any functional +# services are used to ensure the correct working of the framework. For example if it is set to true then eventing and changes +# since are supported out of the box. +job.enabled=true + +# +# This property can be set to disable any modifications to a job by the consumer. It simply lists (comma separated) the +# Job States that are considered an "end state". Once a job is in one of these end states it can no longer be altered by the consumer. +# If this property is not set then the following job states are considered end states: COMPLETED and FAILED. If this property is +# set to "NONE" then it is assumed that there is no end state which also means the consumer can always alter the job. +job.endStates= + +# +# This property indicates if events caused by a consumer operation shall be published to that consumer. If it is set to true +# then consumer caused events will be published. If set to false only events caused by provider operations will be published. +# This will be the default for most cases. If a specific behaviour is required for a particular functional service provider +# then this property can be set for each provider individually by adding "." at the end of this property. +# I.e. job.event.includeConsumerRequested.RolloverStudents=true. The is the name of the service +# as defined by the functional Service URL and not the class name of the implementation class. +# Default: false +job.event.includeConsumerRequested=false + +# This property lists a number of zone|context for which a functional service event must be published, regardless +# of the originator (consumer) who created the job. Generally events are only sent to the originator of the job but the +# SIF3.x specification also allows events to be sent to specific other consumers that have the permission to get job events. +# These are generally "auditing" zones that needs to capture all job activities. This property can specify to which +# additional zone|context a job event is sent. Because it is possible that there are more than one auditing style zones +# this porperty allows for a comma separated list of zone|contexts. Further if a specific behaviour is required for a +# particular functional service provider then this property can be set for each provider individually by adding \ +# "." at the end of this property. I.e. job.event.auditZones.RolloverStudents=abc|DEFAULT. The +# is the name of the service as defined by the functional Service URL and not the class name of the implementation class. +# +# The exact EBNF notation is: [|]{,[|]} +# +# If the contextId is not given then DEFAULT is assumed. + +# Example: zoneABC|contextXYZ,zoneEFG +# For the above example the job events will all be sent to zone 'zoneABC' with context 'contextXYZ' and zone 'zoneEFG' and +# context 'DEFAULT' (note that context is not set for second zone so the DEFAULT context is used. +# +job.event.auditZones= + +# +# If eventing is enabled then job related events, which are maintained within the framework, will be published at the frequency (in +# seconds) given in this property. If not set then the default of 15 minutes is used (900 seconds). To turn off job events then +# this property can be set to 0. If a different frequency is required for each provider then this property can be set for each +# provider individually by adding "." at the end of this property. I.e. job.event.frequency.RolloverStudents=600. +# The is the name of the service as defined by the functional Service URL and not the class name of the +# implementation class. +# Default: 900 (15 minutes) +job.event.frequency=300 + +# +# Once a provider is started a delay might be required before events shall be published. This delay is set here in seconds. If not set or +# provided then the default will be 5 seconds. If a different delay is required for each provider then this property can be set for each +# provider individually by adding "." at the end of this property. I.e. job.event.startup.delay.RolloverStudents=30. +# The is the name of the service as defined by the functional Service URL and not the class name of the +# implementation class. +# This value is in seconds. If the value cannot be set to less than 5 seconds. If it is set to less then it will be defaulted to 5 secs. +# +job.event.startup.delay=5 + +# +# This property indicates what the maximum number of job objects per SIF Event message should be. This value is defaulted to 10 if not set. +# It can be overridden programatically by an implementer if required. It can also be set at the provider level by adding "." +# at the end of this property. I.e. job.event.maxObjects.RolloverStudents=25 The is the name of the service as +# defined by the functional Service URL and not the class name of the implementation class. +# Default: 10 +job.event.maxObjects=3 + +# +# Internally the framework keeps a change log for jobs, so that either events can be published or "changes since" can be supported. +# This property indicates how far back in days the change log shall be kept. Any changes older than that will be cleaned up regularly +# at intervals an times set in the job.housekeeping.trigger property. Default used, if property is not set, is 30 days. +job.changelog.keep.days=45 + +# +# Internally the framework maintains some job information. Some jobs may have been unchanged for a long period of time and can +# be considered "stale". This property indicates what a stale job means in terms for days where no activity was recorded. The +# framework will remove these "unchanged" jobs after the number of days indicated with this property. +# Note: Jobs may also have an expire date. This property does not relate to such date. Expired Jobs will be removed by the framework +# regardless of this property. This property is only used for jobs that don't have an expire date but haven't been changed for the +# given number of days. Default is 30 days. +job.stale.keep.days=45 + +# +# The framework uses a Quartz Cron job to regularly perform some house keeping tasks on functional services jobs such as remove +# old entries from the change log (see job.changelog.keep.days property) and/or removing old expired jobs from the job table. +# This property uses the standard Quartz Cron job notation to indicate when and how often the housekeeping job shall run. If not set +# it will default to "Once a Day at 2am" which equates to "0 0 2 * * ?" as a cron job notation. +job.housekeeping.cron=0 0 3 * * ? +#job.housekeeping.cron=0/20 * * * * ? #------------------------------------------------------------------------------------# #-- Custom Properties: Define any properties you like for your implementation here --#