true if processing should be started
- * upon construction, otherwise false.
+ * @param startProcessing true if processing should be started upon
+ * construction, otherwise false.
*
* @throws Exception If a failure occurs.
*/
- public SzReplicator(SzReplicatorOptions options, boolean startProcessing)
- throws Exception
- {
- this(createSzAutoCoreEnvironment(options),
- true,
- options,
- startProcessing);
+ public SzReplicator(SzReplicatorOptions options, boolean startProcessing) throws Exception {
+ this(createSzAutoCoreEnvironment(options), true, options, startProcessing);
}
/**
* Constructs an instance of {@link SzReplicator} with the specified
- * {@link SzEnvironment} and {@link SzReplicatorOptions} instance.
- * The constructed instance will not manage the specified
- * {@link SzEnvironment} in that it will not attempt {@linkplain
- * SzEnvironment#destroy() destroy} it upon destruction of this instance.
+ * {@link SzEnvironment} and {@link SzReplicatorOptions} instance. The
+ * constructed instance will not manage the specified
+ * {@link SzEnvironment} in that it will not attempt
+ * {@linkplain SzEnvironment#destroy() destroy} it upon destruction of this
+ * instance.
*
*
- * NOTE: Any of the {@linkplain SzReplicatorOptions options}
- * specified pertaining to the creation of an {@link SzAutoCoreEnvironment}
- * will be ignored.
+ * NOTE: Any of the {@linkplain SzReplicatorOptions options} specified
+ * pertaining to the creation of an {@link SzAutoCoreEnvironment} will be
+ * ignored.
*
- * @param environment The {@link SzEnvironment} to use.
+ * @param environment The {@link SzEnvironment} to use.
*
- * @param options The {@link SzReplicatorOptions} instance with which to
- * construct the API server instance.
+ * @param options The {@link SzReplicatorOptions} instance with which to
+ * construct the API server instance.
*
- * @param startProcessing
- * Option for specifying the module name to initialize the Senzing API's
- * with. The default value is {@link
- * SzReplicatorConstants#DEFAULT_INSTANCE_NAME}.
+ * Option for specifying the module name to initialize the Senzing API's with.
+ * The default value is {@link SzReplicatorConstants#DEFAULT_INSTANCE_NAME}.
*
* This option can be specified in the following ways:
*
- * Option for specifying the core settings JSON with which to initialize
- * the Core Senzing SDK. The parameter to this option should be the
- * settings as a JSON object or the path to a file containing the
- * settings JSON.
+ * Option for specifying the core settings JSON with which to initialize the
+ * Core Senzing SDK. The parameter to this option should be the settings as a
+ * JSON object or the path to a file containing the settings JSON.
*
* This option can be specified in the following ways:
*
@@ -104,15 +98,14 @@ public enum SzReplicatorOption implements CommandLineOption
- * This presence of this option determines if the Core Senzing SDK
- * is initialized in verbose mode. The default value if not specified
- * is
- * This option sets the number of threads available for executing
- * Core Senzing SDK functions. The single parameter to this option
- * should be a positive integer. If not specified, then this
- * defaults to {@link SzReplicatorConstants#DEFAULT_CORE_CONCURRENCY},
+ * This option sets the number of threads available for executing Core Senzing
+ * SDK functions. The single parameter to this option should be a positive
+ * integer. If not specified, then this defaults to
+ * {@link SzReplicatorConstants#DEFAULT_CORE_CONCURRENCY},
*
* This option can be specified in the following ways:
*
* If leveraging the default configuration stored in the database, this option
- * is used to specify how often the gRPC server should background check that
- * the current active config is the same as the current default config and
- * update the active config if not. The parameter to this option is specified
- * as an integer:
+ * is used to specify how often the gRPC server should background check that the
+ * current active config is the same as the current default config and update
+ * the active config if not. The parameter to this option is specified as an
+ * integer:
*
* This option can be specified in the following ways:
*
* Use this option to balance the message consumption and processing between
* aggressively keeping the data mart closely in sync with the entity repository
- * and less frequent batch processing to conserve system resources. The value
- * to this option is one of the following:
+ * and less frequent batch processing to conserve system resources. The value to
+ * this option is one of the following:
*
* This option is used to specify the URL to an Amazon SQS queue to be used for
@@ -232,12 +218,13 @@ public enum SzReplicatorOption implements CommandLineOption
* This option can be specified in the following ways:
*
* This option can be specified in the following ways:
@@ -314,15 +302,12 @@ public enum SzReplicatorOption implements CommandLineOption
- * The default value for this option if not specified is {@link
- * SzReplicatorConstants#DEFAULT_CORE_SETTINGS_DATABASE_URI}. This
- * is so it attempts to obtain the database URI from the {@linkplain
- * #CORE_SETTINGS Senzing Core SDK settings}.
+ * The default value for this option if not specified is
+ * {@link SzReplicatorConstants#DEFAULT_CORE_SETTINGS_DATABASE_URI}. This is so
+ * it attempts to obtain the database URI from the {@linkplain #CORE_SETTINGS
+ * Senzing Core SDK settings}.
*/
- DATABASE_URI("--database-uri",
- ENV_PREFIX + "DATA_MART_DATABASE_URI",
- null, 1,
- DEFAULT_CORE_SETTINGS_DATABASE_URI);
+ DATABASE_URI("--database-uri", ENV_PREFIX + "DATA_MART_DATABASE_URI", null, 1, DEFAULT_CORE_SETTINGS_DATABASE_URI);
/**
* Constructs with the specified parameters.
@@ -353,7 +338,7 @@ public enum SzReplicatorOption implements CommandLineOption
+ * Implemented to call {@link SQLClient#isQueueEmpty(Connection)} on the backing
+ * {@link SQLClient}.
+ *
+ * Implemented to call {@link SQLClient#getMessageCount(Connection)} on the
+ * backing {@link SQLClient}.
+ *
+ * Implemented to call {@link SQLClient#insertMessage(Connection,String)} on the
+ * backing {@link SQLClient}.
+ *
- * Implemented to call {@link SQLClient#isQueueEmpty(Connection)}
- * on the backing {@link SQLClient}.
- *
- * Implemented to call {@link SQLClient#getMessageCount(Connection)}
- * on the backing {@link SQLClient}.
- *
- * Implemented to call {@link SQLClient#insertMessage(Connection,String)}
- * on the backing {@link SQLClient}.
- *
- * The configuration is in JSON format:
- *
- *
+ * Overridden to renew the lease when a message is dequeued.
+ *
- * Overridden to renew the lease when a message is dequeued.
- *
- * The configuration is in JSON format:
- *
+ * The configuration is in JSON format:
+ *
+ *
- * {@inheritDoc}
+ * The task parameter key for the entity ID.
*/
- @Override
- public Boolean waitUntilReady(long timeoutMillis)
- throws InterruptedException {
- return AbstractListenerService.this.waitUntilReady(timeoutMillis);
+ public static final String ENTITY_ID_PARAMETER_KEY = "ENTITY_ID";
+
+ /**
+ * The task parameter key for the record ID.
+ */
+ public static final String RECORD_ID_PARAMETER_KEY = "RECORD_ID";
+
+ /**
+ * The task parameter key for the data source.
+ */
+ public static final String DATA_SOURCE_PARAMETER_KEY = "DATA_SOURCE";
+
+ /**
+ * The task parameter key for the interesting entity degrees of separation.
+ */
+ public static final String DEGREES_PARAMETER_KEY = "DEGREES";
+
+ /**
+ * The task parameter key for the interesting entity flags.
+ */
+ public static final String FLAGS_PARAMETER_KEY = "FLAGS";
+
+ /**
+ * The task parameter key for the interesting entity sample records.
+ */
+ public static final String SAMPLE_RECORDS_PARAMETER_KEY = "SAMPLE_RECORDS";
+
+ /**
+ * The task parameter key for the code parameter of notices.
+ */
+ public static final String CODE_PARAMETER_KEY = "CODE";
+
+ /**
+ * The task parameter for the description parameter of notices.
+ */
+ public static final String DESCRIPTION_PARAMETER_KEY = "DESCRIPTION";
+
+ /**
+ * The resource key for locking a record.
+ */
+ public static final String RECORD_RESOURCE_KEY = "RECORD";
+
+ /**
+ * The resource key for locking an entity.
+ */
+ public static final String ENTITY_RESOURCE_KEY = "ENTITY";
+
+ /**
+ * A {@link TaskHandler} implementation that simply delegates to
+ * {@link AbstractListenerService#handleTask(String, Map, int, Scheduler)}.
+ */
+ protected class ListenerTaskHandler implements TaskHandler {
+ /**
+ * Default constructor.
+ */
+ public ListenerTaskHandler() {
+ // do nothing
+ }
+
+ /**
+ * Overridden to call {@link AbstractListenerService#waitUntilReady(long)} on
+ * the parent object.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean waitUntilReady(long timeoutMillis) throws InterruptedException {
+ return AbstractListenerService.this.waitUntilReady(timeoutMillis);
+ }
+
+ /**
+ * Overridden to call
+ * {@link AbstractListenerService#handleTask(String, Map, int, Scheduler)} on
+ * the parent object.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void handleTask(String action, Map
+ * Implemented to return the statistics associated with this instance.
+ *
* {@inheritDoc}
*/
@Override
- public void handleTask(String action,
- Maptrue if processing should be started
- * upon construction, otherwise false.
+ * @param startProcessing true if processing should be started upon
+ * construction, otherwise false.
*
* @throws Exception If a failure occurs.
*/
- public SzReplicator(SzEnvironment environment,
- SzReplicatorOptions options,
- boolean startProcessing)
- throws Exception
- {
+ public SzReplicator(SzEnvironment environment, SzReplicatorOptions options, boolean startProcessing)
+ throws Exception {
this(environment, false, options, startProcessing);
}
@@ -761,25 +741,21 @@ public SzReplicator(SzEnvironment environment,
* Constructs an instance of {@link SzReplicator} with the specified
* {@link SzReplicatorOptions} instance.
*
- * @param environment The {@link SzEnvironment} to use.
+ * @param environment The {@link SzEnvironment} to use.
*
- * @param manageEnv true if this instance should destroy the
- * environment when done, otherwise false.
+ * @param manageEnv true if this instance should destroy the
+ * environment when done, otherwise false.
*
- * @param options The {@link SzReplicatorOptions} instance with which to
- * construct the API server instance.
+ * @param options The {@link SzReplicatorOptions} instance with which to
+ * construct the API server instance.
*
- * @param startProcessing true if processing should be started
- * upon construction, otherwise false.
+ * @param startProcessing true if processing should be started upon
+ * construction, otherwise false.
*
* @throws Exception If a failure occurs.
*/
- protected SzReplicator(SzEnvironment environment,
- boolean manageEnv,
- SzReplicatorOptions options,
- boolean startProcessing)
- throws Exception
- {
+ protected SzReplicator(SzEnvironment environment, boolean manageEnv, SzReplicatorOptions options, boolean startProcessing)
+ throws Exception {
// get the concurrency
if (environment instanceof SzAutoEnvironment) {
SzAutoEnvironment autoEnv = (SzAutoEnvironment) environment;
@@ -795,25 +771,23 @@ protected SzReplicator(SzEnvironment environment,
// set the environment
this.environment = environment;
- this.manageEnv = manageEnv;
+ this.manageEnv = manageEnv;
// proxy the environment
- this.proxyEnvironment = (SzEnvironment)
- ReflectionUtilities.restrictedProxy(this.environment, DESTROY_METHOD);
-
+ this.proxyEnvironment = (SzEnvironment) ReflectionUtilities.restrictedProxy(this.environment, DESTROY_METHOD);
+
// declare the scheduling service class (determine based on database type)
String schedulingServiceClassName = null;
// get the database URI
ConnectionUri databaseUri = options.getDatabaseUri();
-
+
if (databaseUri instanceof SQLiteUri) {
SQLiteUri sqliteUri = (SQLiteUri) databaseUri;
Map--ignore-environment [true|false]
*
*/
- IGNORE_ENVIRONMENT("--ignore-environment", null,
- null, 0, "false"),
+ IGNORE_ENVIRONMENT("--ignore-environment", null, null, 0, "false"),
/**
*
@@ -69,16 +68,13 @@ public enum SzReplicatorOption implements CommandLineOption
*/
- CORE_INSTANCE_NAME("--core-instance-name",
- ENV_PREFIX + "CORE_INSTANCE_NAME",
- null, 1, DEFAULT_INSTANCE_NAME),
+ CORE_INSTANCE_NAME("--core-instance-name", ENV_PREFIX + "CORE_INSTANCE_NAME", null, 1, DEFAULT_INSTANCE_NAME),
/**
*
@@ -87,10 +83,8 @@ public enum SzReplicatorOption implements CommandLineOption
*/
- CORE_SETTINGS("--core-settings",
- ENV_PREFIX + "CORE_SETTINGS",
- List.of("SENZING_ENGINE_CONFIGURATION_JSON"),
- true, 1),
+ CORE_SETTINGS("--core-settings", ENV_PREFIX + "CORE_SETTINGS", List.of("SENZING_ENGINE_CONFIGURATION_JSON"), true,
+ 1),
/**
* SENZING_TOOLS_CORE_CONFIG_ID="{config-id}"
*
*/
- CORE_CONFIG_ID("--core-config-id",
- ENV_PREFIX + "CORE_CONFIG_ID", null, 1),
+ CORE_CONFIG_ID("--core-config-id", ENV_PREFIX + "CORE_CONFIG_ID", null, 1),
/**
* muted (which is equivalent to zero). The parameter
- * to this option may be specified as one of:
+ * This presence of this option determines if the Core Senzing SDK is
+ * initialized in verbose mode. The default value if not specified is
+ * muted (which is equivalent to zero). The parameter to this
+ * option may be specified as one of:
*
*
*/
- CORE_LOG_LEVEL("--core-log-level",
- ENV_PREFIX + "CORE_LOG_LEVEL", null,
- 0, "muted"),
+ CORE_LOG_LEVEL("--core-log-level", ENV_PREFIX + "CORE_LOG_LEVEL", null, 0, "muted"),
/**
* muted - To indicate no logging.verbose - To indicate verbose logging.
@@ -146,61 +137,56 @@ public enum SzReplicatorOption implements CommandLineOption
*/
- CORE_CONCURRENCY("--core-concurrency",
- ENV_PREFIX + "CORE_CONCURRENCY", null,
- 1, DEFAULT_CORE_CONCURRENCY_PARAM),
+ CORE_CONCURRENCY("--core-concurrency", ENV_PREFIX + "CORE_CONCURRENCY", null, 1, DEFAULT_CORE_CONCURRENCY_PARAM),
- /**
+ /**
*
*
- * NOTE: This is option ignored if auto-refresh is disabled because
- * the config was specified via the G2CONFIGFILE in the
- * {@link #CORE_SETTINGS} or if {@link #CORE_CONFIG_ID} has been specified
- * to lock in a specific configuration.
+ * NOTE: This is option ignored if auto-refresh is disabled because the
+ * config was specified via the G2CONFIGFILE in the
+ * {@link #CORE_SETTINGS} or if {@link #CORE_CONFIG_ID} has been specified to
+ * lock in a specific configuration.
*
- *
*/
- REFRESH_CONFIG_SECONDS("--refresh-config-seconds",
- ENV_PREFIX + "REFRESH_CONFIG_SECONDS", null,
- 1, DEFAULT_REFRESH_CONFIG_SECONDS_PARAM),
+ REFRESH_CONFIG_SECONDS("--refresh-config-seconds", ENV_PREFIX + "REFRESH_CONFIG_SECONDS", null, 1,
+ DEFAULT_REFRESH_CONFIG_SECONDS_PARAM),
/**
* --refresh-config-seconds {integer}--refresh-config-seconds {integer}SENZING_TOOLS_REFRESH_CONFIG_SECONDS="{integer}"
- *
* This option can be specified in the following ways:
@@ -211,9 +197,9 @@ public enum SzReplicatorOption implements CommandLineOptionleisurely -- This setting allows for longer gaps between
+ * leisurely -- This setting allows for longer gaps between
* updating the data mart, favoring less frequent batch processing in order to
* conserve system resources.standard -- This is the default and is balance between
+ * standard -- This is the default and is balance between
* conserving system resources and keeping the data mart updated in a reasonably
* timely manner.aggressive -- This setting uses more system resources to
- * aggressively consume and process incoming messages to keep the data mart closely
- * in sync with the least time delay.aggressive -- This setting uses more system resources to
+ * aggressively consume and process incoming messages to keep the data mart
+ * closely in sync with the least time delay.
- *
*/
RABBITMQ_INFO_QUEUE("--rabbit-info-queue", ENV_PREFIX + "RABBITMQ_INFO_QUEUE", null, 1),
@@ -291,20 +279,20 @@ public enum SzReplicatorOption implements CommandLineOption--rabbit-info-uri amqp://user:password@host:port/vhost--rabbit-info-uri amqp://user:password@host:port/vhostSENZING_TOOLS_RABBITMQ_URI="amqp://user:password@host:port/vhost"--rabbit-info-queue {queue-name}SENZING_TOOLS_RABBITMQ_INFO_QUEUE="{queue-name}"SENZING_TOOLS_RABBITMQ_INFO_QUEUE="{queue-name}"{@value PostgreSqlUri#SUPPORTED_FORMAT_1}{@value PostgreSqlUri#SUPPORTED_FORMAT_2}{@value SQLiteUri#SUPPORTED_FORMAT_1}{@value SQLiteUri#SUPPORTED_FORMAT_2}{@value SQLiteUri#SUPPORTED_FORMAT_3}{@value PostgreSqlUri#SUPPORTED_FORMAT_1}{@value PostgreSqlUri#SUPPORTED_FORMAT_2}{@value SQLiteUri#SUPPORTED_FORMAT_1}{@value SQLiteUri#SUPPORTED_FORMAT_2}{@value SQLiteUri#SUPPORTED_FORMAT_3}
- *
* {@value SzCoreSettingsUri#SUPPORTED_FORMAT}{@value SzCoreSettingsUri#SUPPORTED_FORMAT}true if this is a primary option,
* otherwise false.
* @param cmdLineFlag The command-line flag.
* @param envVariable The primary environment variable.
@@ -473,24 +458,24 @@ public Settrue if the schema should be dropped and
* recreated, or false if any existing schema
@@ -33,20 +33,17 @@ protected SchemaBuilder() {
* @throws SQLException If a JDBC failure occurs.
*
*/
- public abstract void ensureSchema(Connection conn, boolean recreate)
- throws SQLException;
+ public abstract void ensureSchema(Connection conn, boolean recreate) throws SQLException;
/**
* Utility method to execute a {@link List} of SQL statements.
*
- * @param conn The {@link Connection} with which to execute the statements.
+ * @param conn The {@link Connection} with which to execute the statements.
* @param sqlList The {@link List} of SQL statements to execute.
*
* @throws SQLException If a JDBC failure occurs.
*/
- protected void executeStatements(Connection conn, Listtrue if the queue is empty,
- * otherwise false.
- *
- * @throws SQLException If a database failure occurs.
+ * Provides an interface for interacting with the message queue used by the
+ * associated {@link SQLConsumer}.
*/
- boolean isEmpty() throws SQLException;
+ public interface MessageQueue {
+ /**
+ * Checks if the message queue is empty.
+ *
+ * @return true if the queue is empty, otherwise
+ * false.
+ *
+ * @throws SQLException If a database failure occurs.
+ */
+ boolean isEmpty() throws SQLException;
+
+ /**
+ * Gets the number of messages currently in the message queue. The returned
+ * value will include leased messages.
+ *
+ * @return The number of messages in the message queue (including leased
+ * messages).
+ *
+ * @throws SQLException If a database failure occurs.
+ */
+ int getMessageCount() throws SQLException;
+
+ /**
+ * Enqueues a message on this {@link MessageQueue} so the associated
+ * {@link SQLConsumer} can consume it.
+ *
+ * @param message The message to enqueue.
+ *
+ * @throws SQLException If a SQL failure occurs.
+ */
+ void enqueueMessage(String message) throws SQLException;
+
+ /**
+ * Gets the associated {@link SQLConsumer}.
+ *
+ * @return The associated {@link SQLConsumer}.
+ */
+ SQLConsumer getSQLConsumer();
+ }
/**
- * Gets the number of messages currently in the message queue. The
- * returned value will include leased messages.
- *
- * @return The number of messages in the message queue (including leased
- * messages).
+ * Provides a {@link MessageQueue} implementation that is backed by the
+ * {@link SQLClient} for the associated {@link SQLConsumer}.
*
- * @throws SQLException If a database failure occurs.
*/
- int getMessageCount() throws SQLException;
+ protected class SimpleMessageQueue implements MessageQueue {
+ /**
+ * The {@link SQLClient} backing this instance.
+ */
+ private SQLClient client = null;
+
+ /**
+ * Constructs with the {@link SQLClient} to use.
+ *
+ * @param client The {@link SQLClient} to use.
+ */
+ protected SimpleMessageQueue(SQLClient client) {
+ this.client = client;
+ }
+
+ /**
+ * {@inheritDoc}
+ * true or false.
*/
- private SQLClient client = null;
+ public static final String CLEAN_DATABASE_KEY = "cleanDatabase";
/**
- * Constructs with the {@link SQLClient} to use.
- *
- * @param client The {@link SQLClient} to use.
+ * The initialization parameter key for obtaining the {@link ConnectionProvider}
+ * to use for connecting to the database from the
+ * {@link ConnectionProvider#REGISTRY}.
*/
- protected SimpleMessageQueue(SQLClient client) {
- this.client = client;
- }
+ public static final String CONNECTION_PROVIDER_KEY = "connectionProvider";
/**
- * {@inheritDoc}
- *
+ * {
+ * "connectionProvider": "<provider-registry-name>",
+ * "cleanDatabase": "<true|false>",
+ * "maximumRetries": "<retry-count>"
+ * "retryWaitTime": "<pause-milliseconds>",
+ * "leaseTime": "<lease-time-seconds>",
+ * "maximumLeaseCount": "<message-count>",
+ * "maximumSleepTime": "<sleep-time-seconds>"
+ * }
+ *
+ *
+ * @param config Configuration string containing the needed information to
+ * connect to connect to the backing database to lease messages
+ * and consume them.
+ *
+ * @throws MessageConsumerSetupException If an initialization failure occurs.
*/
- public void enqueueMessage(String message) throws SQLException {
- Connection conn = null;
- try {
- conn = SQLConsumer.this.getConnection();
+ @Override
+ protected void doInit(JsonObject config) throws MessageConsumerSetupException {
+ try {
+ // check if we are cleaning the database
+ Boolean clean = getConfigBoolean(config, CLEAN_DATABASE_KEY, FALSE);
+
+ // get the connection provider name
+ String providerKey = getConfigString(config, CONNECTION_PROVIDER_KEY, true);
+
+ try {
+ this.connectionProvider = ConnectionProvider.REGISTRY.lookup(providerKey);
+ } catch (NameNotFoundException e) {
+ throw new MessageConsumerSetupException(
+ "No ConnectionProvider was registered to the name specified by the " + "\""
+ + CONNECTION_PROVIDER_KEY + "\" initialization parameter: " + providerKey);
+ }
+
+ // get the failure threshold
+ this.maximumRetries = getConfigInteger(config, MAXIMUM_RETRIES_KEY, 0, DEFAULT_MAXIMUM_RETRIES);
+
+ // get the retry wait time
+ this.retryWaitTime = getConfigLong(config, RETRY_WAIT_TIME_KEY, 0L, DEFAULT_RETRY_WAIT_TIME);
+
+ // get the lease time
+ this.leaseTime = getConfigInteger(config, LEASE_TIME_KEY, 1, DEFAULT_LEASE_TIME);
+
+ // get the maximum lease count
+ this.maximumLeaseCount = getConfigInteger(config, MAXIMUM_LEASE_COUNT_KEY, 1, DEFAULT_MAXIMUM_LEASE_COUNT);
+
+ // get the maximum sleep time
+ this.maximumSleepTime = getConfigInteger(config, MAXIMUM_SLEEP_TIME_KEY, 1, DEFAULT_MAXIMUM_SLEEP_TIME);
- this.client.insertMessage(conn, message);
+ // initialize the SQLClient
+ this.sqlClient = this.initSQLClient();
- conn.commit();
+ // initialize the message queue interface
+ this.messageQueue = this.initMessageQueue();
- } finally {
- conn = close(conn);
- }
+ // ensure the schema exists
+ this.ensureSchema(clean);
+
+ // optionally register the MessageQueue interface
+ this.queueRegistryName = getConfigString(config, QUEUE_REGISTRY_NAME_KEY, false);
+
+ if (this.queueRegistryName != null) {
+ this.registryToken = MESSAGE_QUEUE_REGISTRY.bind(this.queueRegistryName, this.messageQueue);
+ }
+
+ } catch (Exception e) {
+ throw new MessageConsumerSetupException(e);
+ }
}
/**
- * {@inheritDoc}
+ * Gets a JDBC {@link Connection} to use. Typically these are obtained from a
+ * backing pool so repeated calls to this function without closing the
+ * previously obtained {@link Connection} instances could exhaust the pool. This
+ * may block until a {@link Connection} is available.
+ *
+ * @return The {@link Connection} that was obtained.
+ *
+ * @throws SQLException If a JDBC failure occurs.
*/
- public SQLConsumer getSQLConsumer() {
- return SQLConsumer.this;
+ protected Connection getConnection() throws SQLException {
+ return this.connectionProvider.getConnection();
}
- }
-
- /**
- * {@link Registry} used to register the {@link MessageQueue} instances
- * associated with each {@link SQLConsumer}. In order to register the
- * {@link MessageQueue} the {@link #QUEUE_REGISTRY_NAME_KEY} initialization
- * parameter must be provided for the {@link SQLConsumer}.
- */
- public static final Registrytrue or false.
- */
- public static final String CLEAN_DATABASE_KEY = "cleanDatabase";
-
- /**
- * The initialization parameter key for obtaining the {@link
- * ConnectionProvider} to use for connecting to the database from the
- * {@link ConnectionProvider#REGISTRY}.
- */
- public static final String CONNECTION_PROVIDER_KEY = "connectionProvider";
-
- /**
- * The initialization parameter to configure the maximum number of times to
- * retry a failed attempt to select messages from the database before
- * aborting consumption.
- */
- public static final String MAXIMUM_RETRIES_KEY = "maximumRetries";
-
- /**
- * The initialization parameter to configure the number of milliseconds to
- * wait to retry when a failure occurs. This is only matters if the
- * configured {@linkplain #MAXIMUM_RETRIES_KEY failure threshold} is
- * greater than one (1).
- */
- public static final String RETRY_WAIT_TIME_KEY = "retryWaitTime";
-
- /**
- * The initialization parameter to configure the number of seconds
- * messages are leased on the database table before they become available
- * to another consumer instance. If not configured then {@link
- * #DEFAULT_LEASE_TIME} is used. Specifying this initialization parameter
- * allows the clients to override.
- */
- public static final String LEASE_TIME_KEY = "leaseTime";
-
- /**
- * The initialization parameter to configure the maximum number of messages
- * to be leased from the database table at one time. If not configured
- * then {@link #DEFAULT_MAXIMUM_LEASE_COUNT} is used. Specifying this
- * initialization parameter allows clients to override.
- */
- public static final String MAXIMUM_LEASE_COUNT_KEY = "maximumLeaseCount";
-
- /**
- * The initialization parameter to configure the maximum number of
- * seconds to sleep when the database queue is found to be empty
- * in order to avoid a busy loop of constant queries. The actual amount
- * of time used for sleep will progressively increase as the message
- * queue continues to be empty until it equals the configured maximum
- * number of seconds. If not configured then {@link
- * #DEFAULT_MAXIMUM_SLEEP_TIME} is used. Specifying this initialization
- * parameter allows clients to override.
- */
- public static final String MAXIMUM_SLEEP_TIME_KEY = "maximumSleepTime";
-
- /**
- * The default number of times to retry failed SQS requests before aborting
- * consumption. The default value is {@value}. A different value can be set
- * via the {#link #MAXIMUM_RETRIES_KEY} initialization parameter.
- */
- public static final int DEFAULT_MAXIMUM_RETRIES = 0;
-
- /**
- * The default number of milliseconds to wait before retrying the SQS request
- * if the previous request failed. The default value is {@value}. A
- * different value can be set via the {@link #RETRY_WAIT_TIME_KEY}
- * initialization parameter.
- */
- public static final long DEFAULT_RETRY_WAIT_TIME = 1000L;
-
- /**
- * The default number of seconds to lease a message in the database table,
- * preventing other consumers from obtaining it. The default value is {@value}.
- * A different value can be set via the {@link #LEASE_TIME_KEY} initialization
- * parameter.
- */
- public static final int DEFAULT_LEASE_TIME = 1800;
-
- /**
- * The default maximum number of messages to be leased from the database table
- * at one time. The default value is {@value}. A different value can be set
- * via the {@link #MAXIMUM_LEASE_COUNT_KEY} initialization parameter.
- */
- public static final int DEFAULT_MAXIMUM_LEASE_COUNT = 100;
-
- /**
- * The default maximum number of second to sleep when an empty queue is
- * encountered in order to avoid a busy loop of querying the database.
- * The actual amount of time used for sleep will progressively increase
- * as the message queue continues to be empty until it equals the
- * configured maximum number of seconds. The default value is {@value}.
- * A different value can be set via the {@link #MAXIMUM_SLEEP_TIME_KEY}
- * initialization parameter.
- */
- public static final int DEFAULT_MAXIMUM_SLEEP_TIME = 10;
-
- /**
- * Defined constant for one second in milliseconds.
- */
- private static final long ONE_SECOND = 1000L;
-
- /**
- * The {@link ConnectionProvider} to use for obtaining
- * {@link Connection} instances.
- */
- private ConnectionProvider connectionProvider;
-
- /**
- * The {@link MessageQueue} for this instance.
- */
- private MessageQueue messageQueue;
-
- /**
- * The name for binding the {@link #messageQueue} in the {@link
- * #MESSAGE_QUEUE_REGISTRY}.
- */
- private String queueRegistryName = null;
-
- /**
- * The {@link AccessToken} for unbinding the {@link #messageQueue} from the
- * {@link #MESSAGE_QUEUE_REGISTRY}.
- */
- private AccessToken registryToken = null;
-
- /**
- * The {@link SQLClient} to use for interacting with the database.
- */
- private SQLClient sqlClient;
-
- /**
- * The consumption thread for this instance.
- */
- private Thread consumptionThread = null;
-
- /**
- * The maximum number of times to retry failed SQS requests before aborting
- * consumption.
- */
- private int maximumRetries = DEFAULT_MAXIMUM_RETRIES;
-
- /**
- * The number of milliseconds to wait before retrying the SQS request if the
- * previous request failed.
- */
- private long retryWaitTime = DEFAULT_RETRY_WAIT_TIME;
-
- /**
- * The configured number of seconds to lease messages from the database
- * queue before they become available to other consumers.
- */
- private int leaseTime = DEFAULT_LEASE_TIME;
-
- /**
- * The configured maximum number of messages to lease at one time from the
- * database queue.
- */
- private int maximumLeaseCount = DEFAULT_MAXIMUM_LEASE_COUNT;
-
- /**
- * The configured maximum number of second to sleep when an empty queue is
- * encountered in order to avoid a busy loop of querying the database.
- * The actual amount of time used for sleep will progressively increase
- * as the message queue continues to be empty until it equals the
- * configured maximum number of seconds.
- */
- private int maximumSleepTime = DEFAULT_MAXIMUM_SLEEP_TIME;
-
- /**
- * Private default constructor.
- */
- public SQLConsumer() {
- // do nothing
- }
-
- /**
- * Initializes the object. It sets the object up based on configuration
- * passed in.
- *
- * {
- * "connectionProvider": "<provider-registry-name>",
- * "cleanDatabase": "<true|false>",
- * "maximumRetries": "<retry-count>"
- * "retryWaitTime": "<pause-milliseconds>",
- * "leaseTime": "<lease-time-seconds>",
- * "maximumLeaseCount": "<message-count>",
- * "maximumSleepTime": "<sleep-time-seconds>"
- * }
- *
- *
- * @param config Configuration string containing the needed information to
- * connect to connect to the backing database to lease
- * messages and consume them.
- *
- * @throws MessageConsumerSetupException If an initialization failure occurs.
- */
- @Override
- protected void doInit(JsonObject config) throws MessageConsumerSetupException {
- try {
- // check if we are cleaning the database
- Boolean clean = getConfigBoolean(config, CLEAN_DATABASE_KEY, FALSE);
-
- // get the connection provider name
- String providerKey = getConfigString(config,
- CONNECTION_PROVIDER_KEY,
- true);
-
- try {
- this.connectionProvider = ConnectionProvider.REGISTRY.lookup(providerKey);
- } catch (NameNotFoundException e) {
- throw new MessageConsumerSetupException(
- "No ConnectionProvider was registered to the name specified by the "
- + "\"" + CONNECTION_PROVIDER_KEY + "\" initialization parameter: "
- + providerKey);
- }
-
- // get the failure threshold
- this.maximumRetries = getConfigInteger(config,
- MAXIMUM_RETRIES_KEY,
- 0,
- DEFAULT_MAXIMUM_RETRIES);
-
- // get the retry wait time
- this.retryWaitTime = getConfigLong(config,
- RETRY_WAIT_TIME_KEY,
- 0L,
- DEFAULT_RETRY_WAIT_TIME);
-
- // get the lease time
- this.leaseTime = getConfigInteger(config,
- LEASE_TIME_KEY,
- 1,
- DEFAULT_LEASE_TIME);
-
- // get the maximum lease count
- this.maximumLeaseCount = getConfigInteger(config,
- MAXIMUM_LEASE_COUNT_KEY,
- 1,
- DEFAULT_MAXIMUM_LEASE_COUNT);
-
- // get the maximum sleep time
- this.maximumSleepTime = getConfigInteger(config,
- MAXIMUM_SLEEP_TIME_KEY,
- 1,
- DEFAULT_MAXIMUM_SLEEP_TIME);
-
- // initialize the SQLClient
- this.sqlClient = this.initSQLClient();
-
- // initialize the message queue interface
- this.messageQueue = this.initMessageQueue();
-
- // ensure the schema exists
- this.ensureSchema(clean);
-
- // optionally register the MessageQueue interface
- this.queueRegistryName = getConfigString(config,
- QUEUE_REGISTRY_NAME_KEY,
- false);
-
- if (this.queueRegistryName != null) {
- this.registryToken = MESSAGE_QUEUE_REGISTRY.bind(
- this.queueRegistryName, this.messageQueue);
- }
-
- } catch (Exception e) {
- throw new MessageConsumerSetupException(e);
+
+ /**
+ * Determines the {@link SQLClient} to use from the metadata obtained from the
+ * JDBC {@link Connection} via {@link #getConnection()} and returns the
+ * {@link SQLClient} instance.
+ *
+ * @return The {@link SQLClient} to use.
+ *
+ * @throws MessageConsumerSetupException If a failure occurs.
+ */
+ protected SQLClient initSQLClient() throws MessageConsumerSetupException {
+ Connection conn = null;
+ try {
+ // get a connection
+ conn = this.getConnection();
+
+ // set the database type
+ DatabaseType databaseType = DatabaseType.detect(conn);
+
+ // create the SQLClient instance
+ switch (databaseType) {
+ case POSTGRESQL:
+ return new PostgreSQLClient();
+ case SQLITE:
+ return new SQLiteClient();
+ default:
+ throw new MessageConsumerSetupException(
+ "The configured ConnectionProvider is associated with unsupported "
+ + "database type. databaseType=[ " + databaseType + " ]");
+ }
+
+ } catch (SQLException e) {
+ throw new MessageConsumerSetupException("Encountered a SQL failure during initialization.", e);
+
+ } finally {
+ conn = close(conn);
+ }
}
- }
-
- /**
- * Gets a JDBC {@link Connection} to use. Typically these are obtained from
- * a backing pool so repeated calls to this function without closing the
- * previously obtained {@link Connection} instances could exhaust the pool.
- * This may block until a {@link Connection} is available.
- *
- * @return The {@link Connection} that was obtained.
- *
- * @throws SQLException If a JDBC failure occurs.
- */
- protected Connection getConnection() throws SQLException {
- return this.connectionProvider.getConnection();
- }
-
- /**
- * Determines the {@link SQLClient} to use from the metadata obtained
- * from the JDBC {@link Connection} via {@link #getConnection()} and
- * returns the {@link SQLClient} instance.
- *
- * @return The {@link SQLClient} to use.
- *
- * @throws MessageConsumerSetupException If a failure occurs.
- */
- protected SQLClient initSQLClient() throws MessageConsumerSetupException {
- Connection conn = null;
- try {
- // get a connection
- conn = this.getConnection();
-
- // set the database type
- DatabaseType databaseType = DatabaseType.detect(conn);
-
- // create the SQLClient instance
- switch (databaseType) {
- case POSTGRESQL:
- return new PostgreSQLClient();
- case SQLITE:
- return new SQLiteClient();
- default:
- throw new MessageConsumerSetupException(
- "The configured ConnectionProvider is associated with unsupported "
- + "database type. databaseType=[ " + databaseType + " ]");
- }
-
- } catch (SQLException e) {
- throw new MessageConsumerSetupException(
- "Encountered a SQL failure during initialization.", e);
-
- } finally {
- conn = close(conn);
+
+ /**
+ * Gets the {@link SQLClient} used by this instance for interacting with the
+ * backing database. This returns null if the {@link SQLClient} has
+ * not yet been initialized.
+ *
+ * @return The {@link SQLClient} used by this instance for interacting with the
+ * backing database.
+ */
+ protected SQLClient getSQLClient() {
+ return this.sqlClient;
}
- }
-
- /**
- * Gets the {@link SQLClient} used by this instance for interacting with the
- * backing database. This returns null if the {@link SQLClient}
- * has not yet been initialized.
- *
- * @return The {@link SQLClient} used by this instance for interacting with
- * the backing database.
- */
- protected SQLClient getSQLClient() {
- return this.sqlClient;
- }
-
- /**
- * Creates and initializes the {@link MessageQueue} instance to use with
- * this {@link SQLConsumer}.
- *
- * @return The {@link MessageQueue} instance that was created to be used
- * with this {@link SQLConsumer}.
- */
- protected MessageQueue initMessageQueue() {
- return new SimpleMessageQueue(this.getSQLClient());
- }
-
- /**
- * Gets the {@link MessageQueue} interface for interacting with the
- * backing message queue for this {@link SQLConsumer}.
- *
- * @return The {@link MessageQueue} interface for interacting with the
- * backing message queue for this {@link SQLConsumer}.
- */
- public MessageQueue getMessageQueue() {
- return this.messageQueue;
- }
-
- /**
- * Ensures the schema exists and alternatively drops the existing the schema
- * and recreates it. This is called from {@link #doInit(JsonObject)}.
- *
- * @param recreate true if the existing schema should be
- * dropped, otherwise false.
- *
- * @throws SQLException If a failure occurs.
- */
- protected void ensureSchema(boolean recreate) throws SQLException {
- Connection conn = null;
- try {
- // get the connection
- conn = this.getConnection();
-
- // get the SQLClient
- SQLClient sqlClient = this.getSQLClient();
-
- // ensure the schema exists
- sqlClient.ensureSchema(conn, recreate);
-
- } finally {
- conn = close(conn);
+
+ /**
+ * Creates and initializes the {@link MessageQueue} instance to use with this
+ * {@link SQLConsumer}.
+ *
+ * @return The {@link MessageQueue} instance that was created to be used with
+ * this {@link SQLConsumer}.
+ */
+ protected MessageQueue initMessageQueue() {
+ return new SimpleMessageQueue(this.getSQLClient());
}
- }
-
- /**
- * Returns the maximum number times failed attempts to connect to the database
- * will be retried before aborting message consumption. This defaults to {@link
- * #DEFAULT_MAXIMUM_RETRIES} and can be configured via the
- * {@link #MAXIMUM_RETRIES_KEY} configuration parameter.
- *
- * @return The maximum number of times failed attempts to connect to the
- * database
- * will be retried before aborting message consumption.
- */
- public int getMaximumRetries() {
- return this.maximumRetries;
- }
-
- /**
- * Returns the number of milliseconds to wait between database query retries
- * when a failure occurs. This defaults to {@link #DEFAULT_RETRY_WAIT_TIME}
- * and can be configured via the {@link #RETRY_WAIT_TIME_KEY} configuration
- * parameter.
- *
- * @return The number of milliseconds to wait between database query retries
- * when a failure occurs.
- */
- public long getRetryWaitTime() {
- return this.retryWaitTime;
- }
-
- /**
- * Gets the number of seconds messages will be leased from the
- * database queue, preventing other processors from consuming those same
- * messages until the lease has expired.
- *
- * @return The number of seconds messages will be leased from the
- * database queue, preventing other processors from consuming those
- * same messages until the lease has expired.
- */
- public int getLeaseTime() {
- return this.leaseTime;
- }
-
- /**
- * Gets the maximum number of messages to lease from the database queue
- * at one time.
- *
- * @return The maximum number of messages to lease from the database
- * queue at one time.
- */
- public int getMaximumLeaseCount() {
- return this.maximumLeaseCount;
- }
-
- /**
- * Gets the maximum number of seconds to sleep when an empty queue
- * is encountered. The actual amount of time used for sleep will
- * progressively increase as the message queue continues to be empty until
- * it equals the configured maximum number of seconds.
- *
- * @return The maximum number of seconds to sleep when an empty queue
- * is encountered.
- */
- public int getMaximumSleepTime() {
- return this.maximumSleepTime;
- }
-
- /**
- * Creates a virtually unique lease ID.
- *
- * @return A new lease ID to use.
- */
- protected String generateLeaseId() {
- long pid = ProcessHandle.current().pid();
- StringBuilder sb = new StringBuilder();
- sb.append(pid).append("|").append(Instant.now().toString()).append("|");
- sb.append(TextUtilities.randomAlphanumericText(50));
- return sb.toString();
- }
-
- /**
- * Handles an SQL failure and checks if consumption should be aborted.
- *
- * @param failureCount The number of consecutive failures so far.
- * @param failure The {@link Exception} that was thrown if available,
- * otherwise null.
- * @return true if consumption should abort, otherwise
- * false.
- */
- protected boolean handleFailure(int failureCount, Exception failure) {
- // get the maximum number of retries
- int maxRetries = this.getMaximumRetries();
-
- logWarning(failure,
- "FAILURE DETECTED: " + failureCount + " of " + maxRetries
- + " consecutive failure(s)");
-
- // check if we have exceeded the maximum failure count
- if (failureCount > maxRetries) {
- // return true to indicate that we should abort consumption
- return true;
-
- } else {
- // looks like we can retry
- try {
- Thread.sleep(this.getRetryWaitTime());
- } catch (InterruptedException ignore) {
- // ignore the exception
- }
- return false;
+
+ /**
+ * Gets the {@link MessageQueue} interface for interacting with the backing
+ * message queue for this {@link SQLConsumer}.
+ *
+ * @return The {@link MessageQueue} interface for interacting with the backing
+ * message queue for this {@link SQLConsumer}.
+ */
+ public MessageQueue getMessageQueue() {
+ return this.messageQueue;
}
- }
-
- /**
- * Implemented to launch a background thread that will read messages from
- * the database queue and process them.
- *
- * @param processor Processes messages
- *
- * @throws MessageConsumerException If a failure occurs.
- */
- @Override
- protected void doConsume(MessageProcessor processor)
- throws MessageConsumerException {
- this.consumptionThread = new Thread(() -> {
- int failureCount = 0;
- long sleepTime = ONE_SECOND;
- while (this.getState() == CONSUMING) {
- // get the SQLClient
- SQLClient sqlClient = this.getSQLClient();
-
- // generate a lease ID
- String leaseId = this.generateLeaseId();
-
- // get the lease time (in seconds)
- int leaseTime = this.getLeaseTime();
-
- // get the maximum lease count
- int maxLeaseCount = this.getMaximumLeaseCount();
-
- // initialize the messages list
- Listtrue if the existing schema should be dropped,
+ * otherwise false.
+ *
+ * @throws SQLException If a failure occurs.
+ */
+ protected void ensureSchema(boolean recreate) throws SQLException {
+ Connection conn = null;
try {
- // get the connection
- conn = this.getConnection();
+ // get the connection
+ conn = this.getConnection();
+
+ // get the SQLClient
+ SQLClient sqlClient = this.getSQLClient();
+
+ // ensure the schema exists
+ sqlClient.ensureSchema(conn, recreate);
+
+ } finally {
+ conn = close(conn);
+ }
+ }
+
+ /**
+ * Returns the maximum number times failed attempts to connect to the database
+ * will be retried before aborting message consumption. This defaults to
+ * {@link #DEFAULT_MAXIMUM_RETRIES} and can be configured via the
+ * {@link #MAXIMUM_RETRIES_KEY} configuration parameter.
+ *
+ * @return The maximum number of times failed attempts to connect to the
+ * database will be retried before aborting message consumption.
+ */
+ public int getMaximumRetries() {
+ return this.maximumRetries;
+ }
+
+ /**
+ * Returns the number of milliseconds to wait between database query retries
+ * when a failure occurs. This defaults to {@link #DEFAULT_RETRY_WAIT_TIME} and
+ * can be configured via the {@link #RETRY_WAIT_TIME_KEY} configuration
+ * parameter.
+ *
+ * @return The number of milliseconds to wait between database query retries
+ * when a failure occurs.
+ */
+ public long getRetryWaitTime() {
+ return this.retryWaitTime;
+ }
+
+ /**
+ * Gets the number of seconds messages will be leased from the database
+ * queue, preventing other processors from consuming those same messages until
+ * the lease has expired.
+ *
+ * @return The number of seconds messages will be leased from the
+ * database queue, preventing other processors from consuming those same
+ * messages until the lease has expired.
+ */
+ public int getLeaseTime() {
+ return this.leaseTime;
+ }
+
+ /**
+ * Gets the maximum number of messages to lease from the database queue at one
+ * time.
+ *
+ * @return The maximum number of messages to lease from the database queue at
+ * one time.
+ */
+ public int getMaximumLeaseCount() {
+ return this.maximumLeaseCount;
+ }
- // first release any expired leases so we can lease those messages
- int count = sqlClient.releaseExpiredLeases(conn, leaseTime);
+ /**
+ * Gets the maximum number of seconds to sleep when an empty queue is
+ * encountered. The actual amount of time used for sleep will progressively
+ * increase as the message queue continues to be empty until it equals the
+ * configured maximum number of seconds.
+ *
+ * @return The maximum number of seconds to sleep when an empty queue is
+ * encountered.
+ */
+ public int getMaximumSleepTime() {
+ return this.maximumSleepTime;
+ }
- // commit the transaction
- conn.commit();
+ /**
+ * Creates a virtually unique lease ID.
+ *
+ * @return A new lease ID to use.
+ */
+ protected String generateLeaseId() {
+ long pid = ProcessHandle.current().pid();
+ StringBuilder sb = new StringBuilder();
+ sb.append(pid).append("|").append(Instant.now().toString()).append("|");
+ sb.append(TextUtilities.randomAlphanumericText(50));
+ return sb.toString();
+ }
- if (count > 0) {
- logInfo("expired leases on " + count + " messages");
- }
+ /**
+ * Handles an SQL failure and checks if consumption should be aborted.
+ *
+ * @param failureCount The number of consecutive failures so far.
+ * @param failure The {@link Exception} that was thrown if available,
+ * otherwise null.
+ * @return true if consumption should abort, otherwise
+ * false.
+ */
+ protected boolean handleFailure(int failureCount, Exception failure) {
+ // get the maximum number of retries
+ int maxRetries = this.getMaximumRetries();
- // lease messages
- count = sqlClient.leaseMessages(
- conn, leaseId, leaseTime, maxLeaseCount);
+ logWarning(failure, "FAILURE DETECTED: " + failureCount + " of " + maxRetries + " consecutive failure(s)");
- // commit and close the connection so the leases are marked
- // and the connection is available
- conn.commit();
- conn = close(conn);
+ // check if we have exceeded the maximum failure count
+ if (failureCount > maxRetries) {
+ // return true to indicate that we should abort consumption
+ return true;
- // check if we have an empty queue
- if (count == 0) {
- failureCount = 0;
+ } else {
+ // looks like we can retry
try {
- Thread.sleep(sleepTime);
+ Thread.sleep(this.getRetryWaitTime());
} catch (InterruptedException ignore) {
- // do nothing
+ // ignore the exception
}
- sleepTime = sleepTime * 2L;
- long maxSleepTime = ONE_SECOND * ((long) this.getMaximumSleepTime());
+ return false;
+ }
+ }
- if (sleepTime > maxSleepTime) {
- sleepTime = maxSleepTime;
+ /**
+ * Implemented to launch a background thread that will read messages from the
+ * database queue and process them.
+ *
+ * @param processor Processes messages
+ *
+ * @throws MessageConsumerException If a failure occurs.
+ */
+ @Override
+ protected void doConsume(MessageProcessor processor) throws MessageConsumerException {
+ this.consumptionThread = new Thread(() -> {
+ int failureCount = 0;
+ long sleepTime = ONE_SECOND;
+ while (this.getState() == CONSUMING) {
+ // get the SQLClient
+ SQLClient sqlClient = this.getSQLClient();
+
+ // generate a lease ID
+ String leaseId = this.generateLeaseId();
+
+ // get the lease time (in seconds)
+ int leaseTime = this.getLeaseTime();
+
+ // get the maximum lease count
+ int maxLeaseCount = this.getMaximumLeaseCount();
+
+ // initialize the messages list
+ Listnull if the queue's
- * configured value should be used.
- */
- private Integer visibilityTimeout = null;
-
- /**
- * Wait parameter in seconds to SQS in case no messages are waiting to be collected.
- */
- private static final int SQS_WAIT_SECS = 10;
-
- /**
- * Generates a SQS consumer.
- *
- * @return The created {@link SQSConsumer} instance.
- */
- public static SQSConsumer generateSQSConsumer() {
- return new SQSConsumer();
- }
-
- /**
- * Private default constructor.
- */
- public SQSConsumer() {
- // do nothing
- }
-
- /**
- * Initializes the object. It sets the object up based on configuration
- * passed in.
- *
- * {
- * "sqsUrl": "<URL>",
- * "concurrency": "<thread-count>",
- * "failureThreshold": "<failure-threshold>",
- * "retryWaitTime": "<pause-milliseconds>",
- * "visibilityTimeout": "<timeout-seconds>"
- * }
- *
- *
- * @param config Configuration string containing the needed information to
- * connect to SQS.
- *
- * @throws MessageConsumerSetupException If an initialization failure occurs.
- */
- @Override
- protected void doInit(JsonObject config) throws MessageConsumerSetupException
- {
- try {
- // get the SQS URL
- this.sqsUrl = getConfigString(config, SQS_URL_KEY, true);
-
- // get the failure threshold
- this.maximumRetries = getConfigInteger(config,
- MAXIMUM_RETRIES_KEY,
- 0,
- DEFAULT_MAXIMUM_RETRIES);
-
- // get the retry wait time
- this.retryWaitTime = getConfigLong(config,
- RETRY_WAIT_TIME_KEY,
- 0L,
- DEFAULT_RETRY_WAIT_TIME);
-
- // get the visibility timeout
- this.visibilityTimeout = getConfigInteger(config,
- VISIBILITY_TIMEOUT_KEY,
- 1,
- null);
-
- this.sqsClient = SqsClient.builder().build();
-
- } catch (RuntimeException e) {
- throw new MessageConsumerSetupException(e);
+ /**
+ * The initialization parameter for the SQS URL. There is no default value so
+ * this configuration parameter is required.
+ */
+ public static final String SQS_URL_KEY = "sqsUrl";
+
+ /**
+ * The initialization parameter to configure the maximum number of times to
+ * retry a failed SQS request before aborting consumption.
+ */
+ public static final String MAXIMUM_RETRIES_KEY = "maximumRetries";
+
+ /**
+ * The initialization parameter to configure the number of milliseconds to wait
+ * to retry when a failure occurs. This is only matters if the configured
+ * {@linkplain #MAXIMUM_RETRIES_KEY failure threshold} is greater than one (1).
+ */
+ public static final String RETRY_WAIT_TIME_KEY = "retryWaitTime";
+
+ /**
+ * The initialization parameter to configure the number of seconds
+ * messages on the SQS queue are hidden from subsequent retrieve requests after
+ * having been retrieved. If not configured then the value configured on the
+ * queue itself is used. Specifying this initialization parameter allows the
+ * client to override.
+ */
+ public static final String VISIBILITY_TIMEOUT_KEY = "visibilityTimeout";
+
+ /**
+ * The default number of times to retry failed SQS requests before aborting
+ * consumption. The default value is {@value}. A different value can be set via
+ * the {#link #MAXIMUM_RETRIES_KEY} parameter.
+ */
+ public static final int DEFAULT_MAXIMUM_RETRIES = 0;
+
+ /**
+ * The default number of milliseconds to wait before retrying the SQS request if
+ * the previous request failed. The default value is {@value}. A different value
+ * can be set via the {@link #RETRY_WAIT_TIME_KEY} parameter.
+ */
+ public static final long DEFAULT_RETRY_WAIT_TIME = 1000L;
+
+ /**
+ * The SQS URL.
+ */
+ private String sqsUrl;
+
+ /**
+ * The {@link SqsClient} for the connection to SQS.
+ */
+ private SqsClient sqsClient;
+
+ /**
+ * The consumption thread for this instance.
+ */
+ private Thread consumptionThread = null;
+
+ /**
+ * The maximum number of times to retry failed SQS requests before aborting
+ * consumption.
+ */
+ private int maximumRetries = DEFAULT_MAXIMUM_RETRIES;
+
+ /**
+ * The number of milliseconds to wait before retrying the SQS request if the
+ * previous request failed.
+ */
+ private long retryWaitTime = DEFAULT_RETRY_WAIT_TIME;
+
+ /**
+ * The configured visibility timeout or null if the queue's
+ * configured value should be used.
+ */
+ private Integer visibilityTimeout = null;
+
+ /**
+ * Wait parameter in seconds to SQS in case no messages are waiting to be
+ * collected.
+ */
+ private static final int SQS_WAIT_SECS = 10;
+
+ /**
+ * Generates a SQS consumer.
+ *
+ * @return The created {@link SQSConsumer} instance.
+ */
+ public static SQSConsumer generateSQSConsumer() {
+ return new SQSConsumer();
}
- }
-
- /**
- * Returns the maximum number times failed SQS requests will be retried before
- * aborting message consumption. This defaults to {@link
- * #DEFAULT_MAXIMUM_RETRIES} and can be configured via the
- * {@link #MAXIMUM_RETRIES_KEY} configuration parameter.
- *
- * @return The maximum number of times failed SQS requests will be retried
- * before aborting message consumption.
- */
- public int getMaximumRetries() {
- return this.maximumRetries;
- }
-
- /**
- * Returns the number of milliseconds to wait between SQS request retries
- * when a failure occurs. This defaults to {@link #DEFAULT_RETRY_WAIT_TIME}
- * and can be configured via the {@link #RETRY_WAIT_TIME_KEY} configuration
- * parameter.
- *
- * @return The number of milliseconds to wait between SQS request retries
- * when a failure occurs.
- */
- public long getRetryWaitTime() {
- return this.retryWaitTime;
- }
-
- /**
- * Returns the number of seconds messages on the SQS queue are hidden
- * from subsequent retrieve requests after having been retrieved. If this
- * returns null then the value configured on the queue itself
- * is used.
- *
- * @return The number of seconds messages on the SQS queue are hidden
- * from subsequent retrieve requests after having been retrieved, or
- * null if the queue's configured value is used.
- */
- public Integer getVisibilityTimeout() { return this.visibilityTimeout; }
-
- /**
- * Returns the configured SQS URL.
- *
- * @return The configured SQS URL.
- */
- public String getSqsUrl() {
- return this.sqsUrl;
- }
-
- /**
- * Handles an SQS failure and checks if consumption should be aborted.
- *
- * @param failureCount The number of consecutive failures so far.
- * @param response The SQS response, or null if not known.
- * @param failure The {@link Exception} that was thrown if available,
- * otherwise null.
- * @return true if consumption should abort, otherwise
- * false.
- */
- protected boolean handleFailure(int failureCount,
- ReceiveMessageResponse response,
- Exception failure)
- {
- // get the maximum number of retries
- int maxRetries = this.getMaximumRetries();
-
- logWarning(failure,
- "FAILURE DETECTED: " + failureCount + " of " + maxRetries
- + " consecutive failure(s)",
- ((response != null)
- ? ("Received SQS HTTP error response code: "
- + response.sdkHttpResponse().statusCode()
- + " / " + response.sdkHttpResponse().statusText())
- : "*** No HTTP Response ***"),
- "SQS URL: " + this.getSqsUrl());
-
- // check if we have exceeded the maximum failure count
- if (failureCount > maxRetries) {
- // return true to indicate that we should abort consumption
- return true;
-
- } else {
- // looks like we can retry
- try {
- Thread.sleep(this.getRetryWaitTime());
- } catch (InterruptedException ignore) {
- // ignore the exception
- }
- return false;
+
+ /**
+ * Private default constructor.
+ */
+ public SQSConsumer() {
+ // do nothing
}
- }
-
- /**
- * Sets up a SQS consumer and then receives messages from SQS and
- * feeds to service.
- *
- * @param processor Processes messages
- *
- * @throws MessageConsumerException If a failure occurs.
- */
- @Override
- protected void doConsume(MessageProcessor processor)
- throws MessageConsumerException
- {
- this.consumptionThread = new Thread(() -> {
- int failureCount = 0;
- while (this.getState() == CONSUMING) {
+
+ /**
+ * Initializes the object. It sets the object up based on configuration passed
+ * in.
+ *
+ * {
+ * "sqsUrl": "<URL>",
+ * "concurrency": "<thread-count>",
+ * "failureThreshold": "<failure-threshold>",
+ * "retryWaitTime": "<pause-milliseconds>",
+ * "visibilityTimeout": "<timeout-seconds>"
+ * }
+ *
+ *
+ * @param config Configuration string containing the needed information to
+ * connect to SQS.
+ *
+ * @throws MessageConsumerSetupException If an initialization failure occurs.
+ */
+ @Override
+ protected void doInit(JsonObject config) throws MessageConsumerSetupException {
try {
- ReceiveMessageRequest request = ReceiveMessageRequest.builder()
- .queueUrl(this.getSqsUrl())
- .waitTimeSeconds(SQS_WAIT_SECS)
- .visibilityTimeout(this.getVisibilityTimeout())
- .build();
-
- ReceiveMessageResponse response = sqsClient.receiveMessage(request);
-
- // failed obtaining a response
- if (!response.sdkHttpResponse().isSuccessful()) {
- int responseCode = response.sdkHttpResponse().statusCode();
- if (this.handleFailure(++failureCount, response, null)) {
- // destroy and then return to abort consumption
- this.destroy();
- return;
-
- } else {
- // let's retry
- continue;
+ // get the SQS URL
+ this.sqsUrl = getConfigString(config, SQS_URL_KEY, true);
+
+ // get the failure threshold
+ this.maximumRetries = getConfigInteger(config, MAXIMUM_RETRIES_KEY, 0, DEFAULT_MAXIMUM_RETRIES);
+
+ // get the retry wait time
+ this.retryWaitTime = getConfigLong(config, RETRY_WAIT_TIME_KEY, 0L, DEFAULT_RETRY_WAIT_TIME);
+
+ // get the visibility timeout
+ this.visibilityTimeout = getConfigInteger(config, VISIBILITY_TIMEOUT_KEY, 1, null);
+
+ this.sqsClient = SqsClient.builder().build();
+
+ } catch (RuntimeException e) {
+ throw new MessageConsumerSetupException(e);
+ }
+ }
+
+ /**
+ * Returns the maximum number times failed SQS requests will be retried before
+ * aborting message consumption. This defaults to
+ * {@link #DEFAULT_MAXIMUM_RETRIES} and can be configured via the
+ * {@link #MAXIMUM_RETRIES_KEY} configuration parameter.
+ *
+ * @return The maximum number of times failed SQS requests will be retried
+ * before aborting message consumption.
+ */
+ public int getMaximumRetries() {
+ return this.maximumRetries;
+ }
+
+ /**
+ * Returns the number of milliseconds to wait between SQS request retries when a
+ * failure occurs. This defaults to {@link #DEFAULT_RETRY_WAIT_TIME} and can be
+ * configured via the {@link #RETRY_WAIT_TIME_KEY} configuration parameter.
+ *
+ * @return The number of milliseconds to wait between SQS request retries when a
+ * failure occurs.
+ */
+ public long getRetryWaitTime() {
+ return this.retryWaitTime;
+ }
+
+ /**
+ * Returns the number of seconds messages on the SQS queue are hidden
+ * from subsequent retrieve requests after having been retrieved. If this
+ * returns null then the value configured on the queue itself is
+ * used.
+ *
+ * @return The number of seconds messages on the SQS queue are hidden
+ * from subsequent retrieve requests after having been retrieved, or
+ * null if the queue's configured value is used.
+ */
+ public Integer getVisibilityTimeout() {
+ return this.visibilityTimeout;
+ }
+
+ /**
+ * Returns the configured SQS URL.
+ *
+ * @return The configured SQS URL.
+ */
+ public String getSqsUrl() {
+ return this.sqsUrl;
+ }
+
+ /**
+ * Handles an SQS failure and checks if consumption should be aborted.
+ *
+ * @param failureCount The number of consecutive failures so far.
+ * @param response The SQS response, or null if not known.
+ * @param failure The {@link Exception} that was thrown if available,
+ * otherwise null.
+ * @return true if consumption should abort, otherwise
+ * false.
+ */
+ protected boolean handleFailure(int failureCount, ReceiveMessageResponse response, Exception failure) {
+ // get the maximum number of retries
+ int maxRetries = this.getMaximumRetries();
+
+ logWarning(failure, "FAILURE DETECTED: " + failureCount + " of " + maxRetries + " consecutive failure(s)",
+ ((response != null)
+ ? ("Received SQS HTTP error response code: " + response.sdkHttpResponse().statusCode() + " / "
+ + response.sdkHttpResponse().statusText())
+ : "*** No HTTP Response ***"),
+ "SQS URL: " + this.getSqsUrl());
+
+ // check if we have exceeded the maximum failure count
+ if (failureCount > maxRetries) {
+ // return true to indicate that we should abort consumption
+ return true;
+
+ } else {
+ // looks like we can retry
+ try {
+ Thread.sleep(this.getRetryWaitTime());
+ } catch (InterruptedException ignore) {
+ // ignore the exception
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Sets up a SQS consumer and then receives messages from SQS and feeds to
+ * service.
+ *
+ * @param processor Processes messages
+ *
+ * @throws MessageConsumerException If a failure occurs.
+ */
+ @Override
+ protected void doConsume(MessageProcessor processor) throws MessageConsumerException {
+ this.consumptionThread = new Thread(() -> {
+ int failureCount = 0;
+ while (this.getState() == CONSUMING) {
+ try {
+ ReceiveMessageRequest request = ReceiveMessageRequest.builder().queueUrl(this.getSqsUrl())
+ .waitTimeSeconds(SQS_WAIT_SECS).visibilityTimeout(this.getVisibilityTimeout()).build();
+
+ ReceiveMessageResponse response = sqsClient.receiveMessage(request);
+
+ // failed obtaining a response
+ if (!response.sdkHttpResponse().isSuccessful()) {
+ int responseCode = response.sdkHttpResponse().statusCode();
+ if (this.handleFailure(++failureCount, response, null)) {
+ // destroy and then return to abort consumption
+ this.destroy();
+ return;
+
+ } else {
+ // let's retry
+ continue;
+ }
+
+ } else {
+ // reset the consecutive failure count
+ failureCount = 0;
+ }
+
+ // get the messages from the response
+ Listnull.
- *
- * @return The {@link TaskHandler} that was obtained via {@link
- * #initTaskHandler(JsonObject)} during initialization, or
- * null if this instance has not yet been initialized.
- */
- protected TaskHandler getTaskHandler() {
- return this.taskHandler;
- }
-
- /**
- * Creates the {@link TaskHandler} to use with the backing {@link
- * SchedulingService} for handling tasks. This is called from {@link
- * #init(JsonObject)}. By default this returns a new instance of {@link
- * ListenerTaskHandler}.
- *
- * @param config The {@link JsonObject} describing the initialization config.
- * @return The {@link TaskHandler} that was created / initialized.
- * @throws ServiceExecutionException If a failure occurs in creating the
- * {@link TaskHandler}.
- */
- protected TaskHandler initTaskHandler(JsonObject config)
- throws ServiceExecutionException {
- return new ListenerTaskHandler();
- }
-
- /**
- * Checks if this instance is ready to handle tasks and waits for
- * it to be ready for the specified maximum number of milliseconds. Specify
- * a negative number of milliseconds to wait indefinitely or zero (0) to
- * simply check if ready with no waiting. This is used so the {@link
- * SchedulingService} can delay handling tasks until ready.
- *
- * @param timeoutMillis The maximum number of milliseconds to wait for this
- * task handler to become ready, a negative number to
- * wait indefinitely, or zero (0) to simply poll without
- * waiting.
- *
- * @return {@link Boolean#TRUE} if ready to handle tasks, {@link
- * Boolean#FALSE} if not yet ready, and null if due to
- * some failure we will never be ready to handle tasks.
- *
- * @throws InterruptedException If interrupted while waiting.
- */
- protected synchronized Boolean waitUntilReady(long timeoutMillis)
- throws InterruptedException {
- switch (this.getState()) {
- case AVAILABLE:
- return Boolean.TRUE;
- case DESTROYING:
- case DESTROYED:
- return null;
- default:
- if (timeoutMillis < 0L) {
- this.wait();
- } else if (timeoutMillis > 0L) {
- this.wait(timeoutMillis);
- }
- return (this.getState() == State.AVAILABLE) ? TRUE : FALSE;
+
+ /**
+ * Provides a means to set the {@link State} for this instance as a synchronized
+ * method that will notify all upon changing the state.
+ *
+ * @param state The {@link State} for this instance.
+ */
+ protected synchronized void setState(State state) {
+ Objects.requireNonNull(state, "State cannot be null");
+ this.state = state;
+ this.notifyAll();
+ }
+
+ /**
+ * This returns the {@link TaskHandler} that was given to the backing
+ * {@link SchedulingService} for handling tasks during initialization. If this
+ * is called prior to initialization then this returns null.
+ *
+ * @return The {@link TaskHandler} that was obtained via
+ * {@link #initTaskHandler(JsonObject)} during initialization, or
+ * null if this instance has not yet been initialized.
+ */
+ protected TaskHandler getTaskHandler() {
+ return this.taskHandler;
+ }
+
+ /**
+ * Creates the {@link TaskHandler} to use with the backing
+ * {@link SchedulingService} for handling tasks. This is called from
+ * {@link #init(JsonObject)}. By default this returns a new instance of
+ * {@link ListenerTaskHandler}.
+ *
+ * @param config The {@link JsonObject} describing the initialization config.
+ * @return The {@link TaskHandler} that was created / initialized.
+ * @throws ServiceExecutionException If a failure occurs in creating the
+ * {@link TaskHandler}.
+ */
+ protected TaskHandler initTaskHandler(JsonObject config) throws ServiceExecutionException {
+ return new ListenerTaskHandler();
}
- }
-
- /**
- * Called to handle the specified {@link Task} with an optional {@link
- * Scheduler} for scheduling follow-up tasks if that is allowed.
- * Additionally, a multiplicity is specified which, if greater than one (1),
- * may require that the task be handled in a different way depending on the
- * {@linkplain Task#getAction() action} associated with the {@link Task}.
- * Typically, follow-up tasks may not be allowed if the specified
- * {@link Task} is itself a follow-up {@link Task}.
- *
- * @param action The action from the {@link Task} to be handled.
- * @param parameters The {@link Map} of parameters to use with the action
- * to
- * be taken.
- * @param multiplicity The number of times an identical task was scheduled.
- * @param followUpScheduler The {@link Scheduler} for scheduling follow-up
- * tasks, or null if follow-up tasks
- * cannot be scheduled.
- * @throws ServiceExecutionException If a failure occurred in handling the
- * task.
- */
- protected abstract void handleTask(String action,
- Mapnull if due to some failure we
+ * will never be ready to handle tasks.
+ *
+ * @throws InterruptedException If interrupted while waiting.
+ */
+ protected synchronized Boolean waitUntilReady(long timeoutMillis) throws InterruptedException {
+ switch (this.getState()) {
+ case AVAILABLE:
+ return Boolean.TRUE;
+ case DESTROYING:
+ case DESTROYED:
+ return null;
+ default:
+ if (timeoutMillis < 0L) {
+ this.wait();
+ } else if (timeoutMillis > 0L) {
+ this.wait(timeoutMillis);
+ }
+ return (this.getState() == State.AVAILABLE) ? TRUE : FALSE;
+ }
}
- try {
- synchronized (this) {
- // default to an empty JSON object if null
- if (config == null) {
- config = Json.createObjectBuilder().build();
+ /**
+ * Called to handle the specified {@link Task} with an optional
+ * {@link Scheduler} for scheduling follow-up tasks if that is allowed.
+ * Additionally, a multiplicity is specified which, if greater than one (1), may
+ * require that the task be handled in a different way depending on the
+ * {@linkplain Task#getAction() action} associated with the {@link Task}.
+ * Typically, follow-up tasks may not be allowed if the specified {@link Task}
+ * is itself a follow-up {@link Task}.
+ *
+ * @param action The action from the {@link Task} to be handled.
+ * @param parameters The {@link Map} of parameters to use with the action
+ * to be taken.
+ * @param multiplicity The number of times an identical task was scheduled.
+ * @param followUpScheduler The {@link Scheduler} for scheduling follow-up
+ * tasks, or null if follow-up tasks
+ * cannot be scheduled.
+ * @throws ServiceExecutionException If a failure occurred in handling the task.
+ */
+ protected abstract void handleTask(String action, Mapnull,
- * but it may be overridden to return something more sensible for a derived
- * implementation.
- *
- * @return The default {@link JsonObject} configuration with which to
- * initialize the backing {@link SchedulingService}.
- *
- * @see #initSchedulingService(JsonObject)
- * @see #getDefaultSchedulingServiceClassName()
- * @see #SCHEDULING_SERVICE_CLASS_KEY
- * @see #SCHEDULING_SERVICE_CONFIG_KEY
- */
- public JsonObject getDefaultSchedulingServiceConfig() {
- return null;
- }
-
- /**
- * Processes the message described by the specified {@link JsonObject}.
- *
- * @param message The {@link JsonObject} describing the message.
- *
- * @throws ServiceExecutionException If a failure occurs.
- */
- @Override
- public void process(JsonObject message) throws ServiceExecutionException {
- try {
- // check the state
- if (this.getState() != AVAILABLE) {
- throw new IllegalStateException(
- "Cannot process messages when not in the " + AVAILABLE + " state: "
- + state);
- }
-
- // get the scheduler
- Scheduler scheduler = this.schedulingService.createScheduler();
-
- // get the task group
- TaskGroup taskGroup = scheduler.getTaskGroup();
- if (taskGroup == null) {
- throw new IllegalStateException("The TaskGroup should not be null");
- }
-
- // schedule the tasks
- this.scheduleTasks(message, scheduler);
-
- // commit the scheduler tasks
- scheduler.commit();
-
- // wait for the tasks to be completed
- logDebug("AWAITING COMPLETION ON TASK GROUP: " + taskGroup.getTaskCount());
- taskGroup.awaitCompletion();
- logDebug("COMPLETED TASK GROUP: " + taskGroup.getTaskCount());
-
- // determine the state of the group
- TaskGroup.State groupState = taskGroup.getState();
- logDebug("COMPLETED TASK GROUP STATE: " + groupState);
- if (groupState == TaskGroup.State.SUCCESSFUL) {
- return;
- }
-
- // if we get here then we had a failure
- Listnull, but
+ * it may be overridden to return something more sensible for a derived
+ * implementation.
+ *
+ * @return The default {@link JsonObject} configuration with which to initialize
+ * the backing {@link SchedulingService}.
+ *
+ * @see #initSchedulingService(JsonObject)
+ * @see #getDefaultSchedulingServiceClassName()
+ * @see #SCHEDULING_SERVICE_CLASS_KEY
+ * @see #SCHEDULING_SERVICE_CONFIG_KEY
+ */
+ public JsonObject getDefaultSchedulingServiceConfig() {
+ return null;
+ }
+
+ /**
+ * Processes the message described by the specified {@link JsonObject}.
+ *
+ * @param message The {@link JsonObject} describing the message.
+ *
+ * @throws ServiceExecutionException If a failure occurs.
+ */
+ @Override
+ public void process(JsonObject message) throws ServiceExecutionException {
+ try {
+ // check the state
+ if (this.getState() != AVAILABLE) {
+ throw new IllegalStateException(
+ "Cannot process messages when not in the " + AVAILABLE + " state: " + state);
+ }
+
+ // get the scheduler
+ Scheduler scheduler = this.schedulingService.createScheduler();
+
+ // get the task group
+ TaskGroup taskGroup = scheduler.getTaskGroup();
+ if (taskGroup == null) {
+ throw new IllegalStateException("The TaskGroup should not be null");
+ }
+
+ // schedule the tasks
+ this.scheduleTasks(message, scheduler);
+
+ // commit the scheduler tasks
+ scheduler.commit();
+
+ // wait for the tasks to be completed
+ logDebug("AWAITING COMPLETION ON TASK GROUP: " + taskGroup.getTaskCount());
+ taskGroup.awaitCompletion();
+ logDebug("COMPLETED TASK GROUP: " + taskGroup.getTaskCount());
+
+ // determine the state of the group
+ TaskGroup.State groupState = taskGroup.getState();
+ logDebug("COMPLETED TASK GROUP STATE: " + groupState);
+ if (groupState == TaskGroup.State.SUCCESSFUL) {
+ return;
+ }
+
+ // if we get here then we had a failure
+ Listnull if no action should
- * be mapped to the {@link MessagePart}.
- */
- protected String getActionForMessagePart(MessagePart messagePart) {
- return this.messagePartMap.get(messagePart);
- }
-
- /**
- * This method is called for the data source code and record ID found in the
- * root of the INFO message. If {@link
- * #getActionForMessagePart(MessagePart)} returns null for
- * {@link MessagePart#RECORD} then this method does nothing (but
- * may be overridden), otherwise if there is an associated task for the
- * {@link MessagePart#RECORD}, then a new {@link Task} is scheduled using the
- * specified {@link Scheduler} with the associated action key and the
- * following parameters and required resources:
- *
- *
- *
- * @param dataSource The {@link String} data source code from the info message.
- * @param recordId The {@link String} record ID from the info message.
- * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
- * @param rawMessage The entire INFO message in its raw form.
- * @param scheduler The {@link Scheduler} to be used to schedule the task.
- */
- protected void handleRecord(String dataSource,
- String recordId,
- SzInfoMessage infoMessage,
- JsonObject rawMessage,
- Scheduler scheduler) {
- String action = this.messagePartMap.get(MessagePart.RECORD);
- if (action == null || action.trim().length() == 0) {
- return;
+
+ /**
+ * Returns the {@link String} action identifier for the specified
+ * {@link MessagePart}. The default implementation of this uses the {@link Map}
+ * with which this instance was constructed.
+ *
+ * @param messagePart The {@link MessagePart} for which the action is being
+ * requested.
+ * @return The associated action or null if no action should be
+ * mapped to the {@link MessagePart}.
+ */
+ protected String getActionForMessagePart(MessagePart messagePart) {
+ return this.messagePartMap.get(messagePart);
}
- scheduler.createTaskBuilder(action)
- .parameter(DATA_SOURCE_PARAMETER_KEY, dataSource)
- .parameter(RECORD_ID_PARAMETER_KEY, recordId)
- .resource(RECORD_RESOURCE_KEY, dataSource, recordId)
- .schedule();
- }
-
- /**
- * This method is called for each entity ID in the
- * AFFECTED_ENTITIES found in an INFO message. If
- * {@link #getActionForMessagePart(MessagePart)} returns null for
- * {@link MessagePart#AFFECTED_ENTITY} then this method does nothing (but
- * may be overridden), otherwise if there is an associated task for the
- * {@link MessagePart#AFFECTED_ENTITY}, then a new {@link Task} is
- * scheduled using the specified {@link Scheduler} with the associated
- * action key and the following parameters and required resources:
- *
- *
- *
- * @param entityId The {@link Long} entity ID identifying the affected
- * entity.
- * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
- * @param rawMessagePart The {@link JsonObject} describing the interesting
- * entity in its raw JSON form.
- * @param rawMessage The entire INFO message in its raw form.
- * @param scheduler The {@link Scheduler} to be used to schedule the task.
- */
- protected void handleAffected(long entityId,
- SzInfoMessage infoMessage,
- JsonObject rawMessagePart,
- JsonObject rawMessage,
- Scheduler scheduler) {
- String action = this.messagePartMap.get(MessagePart.AFFECTED_ENTITY);
- if (action == null || action.trim().length() == 0) {
- return;
+
+ /**
+ * This method is called for the data source code and record ID found in the
+ * root of the INFO message. If {@link #getActionForMessagePart(MessagePart)}
+ * returns null for {@link MessagePart#RECORD} then this method
+ * does nothing (but may be overridden), otherwise if there is an associated
+ * task for the {@link MessagePart#RECORD}, then a new {@link Task} is scheduled
+ * using the specified {@link Scheduler} with the associated action key and the
+ * following parameters and required resources:
+ *
+ *
+ *
+ * @param dataSource The {@link String} data source code from the info message.
+ * @param recordId The {@link String} record ID from the info message.
+ * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
+ * @param rawMessage The entire INFO message in its raw form.
+ * @param scheduler The {@link Scheduler} to be used to schedule the task.
+ */
+ protected void handleRecord(String dataSource, String recordId, SzInfoMessage infoMessage, JsonObject rawMessage, Scheduler scheduler) {
+ String action = this.messagePartMap.get(MessagePart.RECORD);
+ if (action == null || action.trim().length() == 0) {
+ return;
+ }
+ scheduler.createTaskBuilder(action).parameter(DATA_SOURCE_PARAMETER_KEY, dataSource)
+ .parameter(RECORD_ID_PARAMETER_KEY, recordId).resource(RECORD_RESOURCE_KEY, dataSource, recordId)
+ .schedule();
}
- scheduler.createTaskBuilder(action)
- .parameter(ENTITY_ID_PARAMETER_KEY, entityId)
- .resource(ENTITY_RESOURCE_KEY, entityId)
- .schedule();
- }
-
- /**
- * This method is called for each element in the
- * INTERESTING_ENTITIES found in an INFO message. If
- * {@link #getActionForMessagePart(MessagePart)} returns null for
- * {@link MessagePart#INTERESTING_ENTITY} then this method does nothing (but
- * may be overridden), otherwise if there is an associated task for the
- * {@link MessagePart#INTERESTING_ENTITY}, then a new {@link Task} is
- * scheduled using the specified {@link Scheduler} with the associated
- * action key and the following parameters and required resources:
- *
- *
- *
- * @param interestingEntity The {@link SzInterestingEntity} describing the
- * interesting entity to handle.
- * @param infoMessage The {@link SzInfoMessage} describing the INFO
- * message.
- * @param rawMessagePart The {@link JsonObject} describing the interesting
- * entity in its raw JSON form.
- * @param rawMessage The entire INFO message in its raw form.
- * @param scheduler The {@link Scheduler} to be used to schedule the
- * task.
- */
- protected void handleInteresting(SzInterestingEntity interestingEntity,
- SzInfoMessage infoMessage,
- JsonObject rawMessagePart,
- JsonObject rawMessage,
- Scheduler scheduler) {
- String action = this.messagePartMap.get(MessagePart.INTERESTING_ENTITY);
- if (action == null || action.trim().length() == 0) {
- return;
+
+ /**
+ * This method is called for each entity ID in the
+ * AFFECTED_ENTITIES found in an INFO message. If
+ * {@link #getActionForMessagePart(MessagePart)} returns null for
+ * {@link MessagePart#AFFECTED_ENTITY} then this method does nothing (but may be
+ * overridden), otherwise if there is an associated task for the
+ * {@link MessagePart#AFFECTED_ENTITY}, then a new {@link Task} is scheduled
+ * using the specified {@link Scheduler} with the associated action key and the
+ * following parameters and required resources:
+ *
+ *
+ *
+ * @param entityId The {@link Long} entity ID identifying the affected
+ * entity.
+ * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
+ * @param rawMessagePart The {@link JsonObject} describing the interesting
+ * entity in its raw JSON form.
+ * @param rawMessage The entire INFO message in its raw form.
+ * @param scheduler The {@link Scheduler} to be used to schedule the task.
+ */
+ protected void handleAffected(long entityId, SzInfoMessage infoMessage, JsonObject rawMessagePart, JsonObject rawMessage, Scheduler scheduler) {
+ String action = this.messagePartMap.get(MessagePart.AFFECTED_ENTITY);
+ if (action == null || action.trim().length() == 0) {
+ return;
+ }
+ scheduler.createTaskBuilder(action).parameter(ENTITY_ID_PARAMETER_KEY, entityId)
+ .resource(ENTITY_RESOURCE_KEY, entityId).schedule();
}
- // begin building the task with the basic parameters
- TaskBuilder.ListParamBuilder builder = scheduler.createTaskBuilder(action)
- .parameter(ENTITY_ID_PARAMETER_KEY, interestingEntity.getEntityId())
- .parameter(DEGREES_PARAMETER_KEY, interestingEntity.getDegrees())
- .listParameter(FLAGS_PARAMETER_KEY);
+ /**
+ * This method is called for each element in the
+ * INTERESTING_ENTITIES found in an INFO message. If
+ * {@link #getActionForMessagePart(MessagePart)} returns null for
+ * {@link MessagePart#INTERESTING_ENTITY} then this method does nothing (but may
+ * be overridden), otherwise if there is an associated task for the
+ * {@link MessagePart#INTERESTING_ENTITY}, then a new {@link Task} is scheduled
+ * using the specified {@link Scheduler} with the associated action key and the
+ * following parameters and required resources:
+ *
+ *
+ *
+ * @param interestingEntity The {@link SzInterestingEntity} describing the
+ * interesting entity to handle.
+ * @param infoMessage The {@link SzInfoMessage} describing the INFO
+ * message.
+ * @param rawMessagePart The {@link JsonObject} describing the interesting
+ * entity in its raw JSON form.
+ * @param rawMessage The entire INFO message in its raw form.
+ * @param scheduler The {@link Scheduler} to be used to schedule the
+ * task.
+ */
+ protected void handleInteresting(SzInterestingEntity interestingEntity, SzInfoMessage infoMessage, JsonObject rawMessagePart, JsonObject rawMessage, Scheduler scheduler) {
+ String action = this.messagePartMap.get(MessagePart.INTERESTING_ENTITY);
+ if (action == null || action.trim().length() == 0) {
+ return;
+ }
+
+ // begin building the task with the basic parameters
+ TaskBuilder.ListParamBuilder builder = scheduler.createTaskBuilder(action)
+ .parameter(ENTITY_ID_PARAMETER_KEY, interestingEntity.getEntityId())
+ .parameter(DEGREES_PARAMETER_KEY, interestingEntity.getDegrees()).listParameter(FLAGS_PARAMETER_KEY);
- // add the flags to the list parameter
- for (String flag : interestingEntity.getFlags()) {
- builder.add(flag);
- }
+ // add the flags to the list parameter
+ for (String flag : interestingEntity.getFlags()) {
+ builder.add(flag);
+ }
+
+ // end the list and start the sample records parameter
+ builder = builder.endList().listParameter(SAMPLE_RECORDS_PARAMETER_KEY);
- // end the list and start the sample records parameter
- builder = builder.endList().listParameter(SAMPLE_RECORDS_PARAMETER_KEY);
+ // add the sample records to the list parameter
+ for (SzSampleRecord record : interestingEntity.getSampleRecords()) {
+ builder.add(record.toJsonObject());
+ }
- // add the sample records to the list parameter
- for (SzSampleRecord record : interestingEntity.getSampleRecords()) {
- builder.add(record.toJsonObject());
+ // end the list, add the required resources and schedule the task
+ builder.endList().resource(ENTITY_RESOURCE_KEY, interestingEntity.getEntityId()).schedule();
}
- // end the list, add the required resources and schedule the task
- builder.endList()
- .resource(ENTITY_RESOURCE_KEY, interestingEntity.getEntityId())
- .schedule();
- }
-
- /**
- * This method is called for each element in the NOTICES array
- * found in an INFO message. If {@link #getActionForMessagePart(MessagePart)}
- * returns null for {@link MessagePart#NOTICE} then this method
- * does nothing (but may be overridden), otherwise if there is an associated
- * task for the {@link MessagePart#NOTICE}, then a new {@link Task} is
- * scheduled using the specified {@link Scheduler} with the associated
- * action key and the following parameters and required resources:
- *
- *
- *
- * @param notice The {@link SzNotice} describing the notice.
- * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
- * @param rawMessagePart The {@link JsonObject} describing the notice in its
- * raw JSON form.
- * @param rawMessage The entire INFO message.
- * @param scheduler The {@link Scheduler} to be used to schedule the task.
- */
- protected void handleNotice(SzNotice notice,
- SzInfoMessage infoMessage,
- JsonObject rawMessagePart,
- JsonObject rawMessage,
- Scheduler scheduler) {
- String action = this.getActionForMessagePart(MessagePart.NOTICE);
- if (action == null || action.trim().length() == 0) {
- return;
+ /**
+ * This method is called for each element in the NOTICES array
+ * found in an INFO message. If {@link #getActionForMessagePart(MessagePart)}
+ * returns null for {@link MessagePart#NOTICE} then this method
+ * does nothing (but may be overridden), otherwise if there is an associated
+ * task for the {@link MessagePart#NOTICE}, then a new {@link Task} is scheduled
+ * using the specified {@link Scheduler} with the associated action key and the
+ * following parameters and required resources:
+ *
+ *
+ *
+ * @param notice The {@link SzNotice} describing the notice.
+ * @param infoMessage The {@link SzInfoMessage} describing the INFO message.
+ * @param rawMessagePart The {@link JsonObject} describing the notice in its raw
+ * JSON form.
+ * @param rawMessage The entire INFO message.
+ * @param scheduler The {@link Scheduler} to be used to schedule the task.
+ */
+ protected void handleNotice(SzNotice notice, SzInfoMessage infoMessage, JsonObject rawMessagePart, JsonObject rawMessage, Scheduler scheduler) {
+ String action = this.getActionForMessagePart(MessagePart.NOTICE);
+ if (action == null || action.trim().length() == 0) {
+ return;
+ }
+ scheduler.createTaskBuilder(action).parameter(CODE_PARAMETER_KEY, notice.getCode())
+ .parameter(DESCRIPTION_PARAMETER_KEY, notice.getDescription()).schedule();
}
- scheduler.createTaskBuilder(action)
- .parameter(CODE_PARAMETER_KEY, notice.getCode())
- .parameter(DESCRIPTION_PARAMETER_KEY, notice.getDescription())
- .schedule();
- }
-
- /**
- * Implemented as a synchronized method to {@linkplain #setState(State)
- * set the state} to {@link com.senzing.listener.communication.MessageConsumer.State#DESTROYING}, call
- * {@link #doDestroy()} and
- * then perform {@link #notifyAll()} and set the state to {@link
- * com.senzing.listener.communication.MessageConsumer.State#DESTROYED}.
- */
- public void destroy() {
- synchronized (this) {
- State state = this.getState();
- if (state == DESTROYED) {
- return;
- }
-
- if (state == DESTROYING) {
- while (this.getState() != DESTROYED) {
- try {
- this.wait(1000L);
- } catch (InterruptedException e) {
- // ignore
- }
+
+ /**
+ * Implemented as a synchronized method to {@linkplain #setState(State) set the
+ * state} to
+ * {@link com.senzing.listener.communication.MessageConsumer.State#DESTROYING},
+ * call {@link #doDestroy()} and then perform {@link #notifyAll()} and set the
+ * state to
+ * {@link com.senzing.listener.communication.MessageConsumer.State#DESTROYED}.
+ */
+ public void destroy() {
+ synchronized (this) {
+ State state = this.getState();
+ if (state == DESTROYED) {
+ return;
+ }
+
+ if (state == DESTROYING) {
+ while (this.getState() != DESTROYED) {
+ try {
+ this.wait(1000L);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ // once DESTROYED state is found, just return
+ return;
+ }
+
+ // begin destruction
+ this.setState(DESTROYING);
}
- // once DESTROYED state is found, just return
- return;
- }
- // begin destruction
- this.setState(DESTROYING);
- }
+ // destroy the scheduling service
+ this.schedulingService.destroy();
- // destroy the scheduling service
- this.schedulingService.destroy();
+ try {
+ // now complete the destruction / cleanup
+ this.doDestroy();
- try {
- // now complete the destruction / cleanup
- this.doDestroy();
+ } finally {
+ this.setState(DESTROYED); // this should notify all as well
+ }
+ }
- } finally {
- this.setState(DESTROYED); // this should notify all as well
+ /**
+ * Gets the backing {@link SchedulingService} used by this instance.
+ *
+ * @return The backing {@link SchedulingService} used by this instance.
+ */
+ protected SchedulingService getSchedulingService() {
+ return this.schedulingService;
}
- }
-
- /**
- * Gets the backing {@link SchedulingService} used by this instance.
- *
- * @return The backing {@link SchedulingService} used by this instance.
- */
- protected SchedulingService getSchedulingService() {
- return this.schedulingService;
- }
-
- /**
- * This is called from the {@link #destroy()} implementation and should be
- * overridden by the concrete sub-class.
- */
- protected abstract void doDestroy();
+
+ /**
+ * This is called from the {@link #destroy()} implementation and should be
+ * overridden by the concrete sub-class.
+ */
+ protected abstract void doDestroy();
}
diff --git a/src/main/java/com/senzing/listener/service/locking/LockToken.java b/src/main/java/com/senzing/listener/service/locking/LockToken.java
index 2b13df3..47b4af6 100644
--- a/src/main/java/com/senzing/listener/service/locking/LockToken.java
+++ b/src/main/java/com/senzing/listener/service/locking/LockToken.java
@@ -1,5 +1,7 @@
package com.senzing.listener.service.locking;
+import static com.senzing.util.LoggingUtilities.formatStackTrace;
+
import java.io.Serializable;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -14,342 +16,332 @@
* Identifies a lock that has been obtained.
*/
public final class LockToken implements Serializable {
- /**
- * The pattern for parsing the date values returned from the native API.
- */
- private static final String DATE_TIME_PATTERN = "yyyy-MM-dd_HH:mm:ss.SSS";
-
- /**
- * The time zone used for the time component of the build number.
- */
- private static final ZoneId UTC_ZONE = ZoneId.of("UTC");
-
- /**
- * The {@link DateTimeFormatter} for interpreting the timestamps from the
- * native API.
- */
- private static final DateTimeFormatter DATE_TIME_FORMATTER
- = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
-
- /**
- * The next lock token ID.
- */
- private static long nextTokenId = 1L;
-
- /**
- * The server key for this this server.
- */
- private static final String LOCAL_HOST_KEY = formatHostKey();
-
- /**
- * The {@link LockScope} for this instance.
- */
- private LockScope scope;
-
- /**
- * The key that identifies the process on the server/host on where the lock
- * was obtained.
- */
- private String processKey;
-
- /**
- * The key that identifies the host on which the lock was obtained.
- */
- private String hostKey;
-
- /**
- * The unique sequential numeric ID for this instance token instance.
- */
- private long tokenId;
-
- /**
- * The token key for this token which encapsulates the unique ID and the
- * timestamp when it was created.
- */
- private String tokenKey;
-
- /**
- * The timestamp for when this instance was constructed.
- */
- private Instant timestamp;
-
- /**
- * Returns the next token ID.
- *
- * @return The next unique token ID.
- */
- private synchronized long getNextTokenId() {
- return nextTokenId++;
- }
-
- /**
- * Constructs with the specified {@link LockScope}.
- *
- * @param scope The {@link LockScope} for the token.
- */
- public LockToken(LockScope scope) {
- Objects.requireNonNull(scope, "The scope cannot be null");
- this.scope = scope;
- ProcessHandle procHandle = ProcessHandle.current();
- long processId = procHandle.pid();
- this.processKey = String.valueOf(processId);
- ProcessHandle.Info procInfo = procHandle.info();
- Optionaltrue if and only if the specified
- * parameter is a non-null reference to an object of the same class with
- * equivalent properties.
- *
- * @param obj The object to compare with.
- * @return true if the objects are equal, otherwise
- * false.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
+
+ /**
+ * Gets the {@link LockScope} describing the scope of the lock identified by
+ * this lock token.
+ *
+ * @return The {@link LockScope} describing the scope of the lock identified by
+ * this lock token.
+ */
+ public LockScope getScope() {
+ return this.scope;
}
- if (this == obj) {
- return true;
+
+ /**
+ * Gets the unique (within process) sequential token ID for this instance.
+ *
+ * @return The unique (within process) sequential token ID for this instance.
+ */
+ public long getTokenId() {
+ return this.tokenId;
}
- if (obj.getClass() != this.getClass()) {
- return false;
+
+ /**
+ * Gets the {@link Instant} timestamp when this instance was constructed.
+ *
+ * @return The {@link Instant} timestamp when this instance was constructed.
+ */
+ public Instant getTimestamp() {
+ return this.timestamp;
}
- LockToken that = (LockToken) obj;
- if (!Objects.equals(this.getScope(), that.getScope())) {
- return false;
+
+ /**
+ * Gets the encoded {@link String} key for the process in which the lock token
+ * instance was originally constructed.
+ *
+ * @return The encoded {@link String} key for the process in which the lock
+ * token instance was originally constructed.
+ */
+ public String getProcessKey() {
+ return this.processKey;
}
- if (!Objects.equals(this.getTokenId(), that.getTokenId())) {
- return false;
+
+ /**
+ * Gets the encoded {@link String} key for the host/server on which the lock
+ * token instance was originally constructed.
+ *
+ * @return The encoded {@link String} key for the host/server on which the lock
+ * token instance was originally constructed.
+ */
+ public String getHostKey() {
+ return this.hostKey;
}
- if (!Objects.equals(this.getTimestamp(), that.getTimestamp())) {
- return false;
+
+ /**
+ * Gets the full formatted token key which formats the elements of this lock
+ * token into a unique descriptive {@link String} describing when, where and how
+ * the resource is locked. This can be used to uniquely represent this
+ * {@link LockToken} as a {@link String}.
+ *
+ * @return The unique token key for this instance.
+ */
+ public String getTokenKey() {
+ return this.tokenKey;
}
- if (!Objects.equals(this.getProcessKey(), that.getProcessKey())) {
- return false;
+
+ /**
+ * Overridden to return true if and only if the specified parameter
+ * is a non-null reference to an object of the same class with equivalent
+ * properties.
+ *
+ * @param obj The object to compare with.
+ * @return true if the objects are equal, otherwise
+ * false.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (obj.getClass() != this.getClass()) {
+ return false;
+ }
+ LockToken that = (LockToken) obj;
+ if (!Objects.equals(this.getScope(), that.getScope())) {
+ return false;
+ }
+ if (!Objects.equals(this.getTokenId(), that.getTokenId())) {
+ return false;
+ }
+ if (!Objects.equals(this.getTimestamp(), that.getTimestamp())) {
+ return false;
+ }
+ if (!Objects.equals(this.getProcessKey(), that.getProcessKey())) {
+ return false;
+ }
+ if (!Objects.equals(this.getHostKey(), that.getHostKey())) {
+ return false;
+ }
+ if (!Objects.equals(this.getTokenKey(), that.getTokenKey())) {
+ return false;
+ }
+ return true;
}
- if (!Objects.equals(this.getHostKey(), that.getHostKey())) {
- return false;
+
+ /**
+ * Overridden to return a hash code that is consistent with the
+ * {@link #equals(Object)} method.
+ *
+ * @return The hash code for this instance.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.getScope(), this.getTokenId(), this.getTimestamp(), this.getProcessKey(),
+ this.getHostKey(), this.getTokenKey());
}
- if (!Objects.equals(this.getTokenKey(), that.getTokenKey())) {
- return false;
+
+ /**
+ * Overridden to return the result from {@link #getTokenKey()}.
+ *
+ * @return The result from {@link #getTokenKey()}.
+ */
+ public String toString() {
+ return this.getTokenKey();
}
- return true;
- }
-
- /**
- * Overridden to return a hash code that is consistent with the {@link
- * #equals(Object)} method.
- *
- * @return The hash code for this instance.
- */
- @Override
- public int hashCode() {
- return Objects.hash(this.getScope(),
- this.getTokenId(),
- this.getTimestamp(),
- this.getProcessKey(),
- this.getHostKey(),
- this.getTokenKey());
- }
-
- /**
- * Overridden to return the result from {@link #getTokenKey()}.
- *
- * @return The result from {@link #getTokenKey()}.
- */
- public String toString() {
- return this.getTokenKey();
- }
-
- /**
- * Test main method to create tokens and print out their token keys.
- * @param args The command-line arguments.
- */
- public static void main(String[] args) {
- try {
- LockScope[] scopes = LockScope.values();
- for (int index = 0; index < 10; index++) {
- LockToken lockToken = new LockToken(scopes[index % 3]);
- System.out.println();
- System.out.println("---------------------------------------------");
- System.out.println(lockToken);
- }
- } catch (Exception e) {
- e.printStackTrace();
+
+ /**
+ * Test main method to create tokens and print out their token keys.
+ *
+ * @param args The command-line arguments.
+ */
+ public static void main(String[] args) {
+ try {
+ LockScope[] scopes = LockScope.values();
+ for (int index = 0; index < 10; index++) {
+ LockToken lockToken = new LockToken(scopes[index % 3]);
+ System.out.println();
+ System.out.println("---------------------------------------------");
+ System.out.println(lockToken);
+ }
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ System.err.println(formatStackTrace(e.getStackTrace()));
+ }
}
- }
}
diff --git a/src/main/java/com/senzing/listener/service/locking/ResourceKey.java b/src/main/java/com/senzing/listener/service/locking/ResourceKey.java
index 1f186fc..0fa68ac 100644
--- a/src/main/java/com/senzing/listener/service/locking/ResourceKey.java
+++ b/src/main/java/com/senzing/listener/service/locking/ResourceKey.java
@@ -12,258 +12,255 @@
import java.util.Objects;
import static com.senzing.io.IOUtilities.UTF_8;
+import static com.senzing.util.LoggingUtilities.formatStackTrace;
/**
* Provides a key for identifying a resource that can be locked via the
* {@link LockingService}.
*/
public final class ResourceKey implements Serializable, Comparable"RECORD", "ENTITY", or
- * "REPORT") followed by zero or more key components that more
- * specifically identify the resource within the type of resource (e.g.: an
- * entity ID or a data source code followed by a record ID).
- *
- * @param resourceType The type of resource being identified.
- * @param components Zero or more key components that more specifically
- * identify the resource.
- * @throws NullPointerException If the resource type is null.
- */
- public ResourceKey(String resourceType, String... components) {
- Objects.requireNonNull(resourceType);
- this.resourceType = resourceType;
- this.components = (components == null) ? List.of() : List.of(components);
- }
-
- /**
- * Constructs with the {@link String} key identifying the type of resource
- * (e.g.: "RECORD", "ENTITY", or
- * "REPORT") followed by zero or more key components that more
- * specifically identify the resource within the type of resource (e.g.: an
- * entity ID or a data source code followed by a record ID). This constructor
- * version converts the one or more {@link Object} component instances to
- * {@link String} instances.
- *
- * @param resourceType The type of resource being identified.
- * @param components Zero or more key components that more specifically
- * identify the resource.
- * @throws NullPointerException If the resource type is null.
- */
- public ResourceKey(String resourceType, Object... components) {
- Objects.requireNonNull(resourceType);
- this.resourceType = resourceType;
- if (components == null) {
- this.components = List.of();
- } else {
- this.components = new ArrayList<>(components.length);
- for (Object comp : components) {
- this.components.add(String.valueOf(comp));
- }
- this.components = Collections.unmodifiableList(this.components);
+ /**
+ * Constructs with the {@link String} key identifying the type of resource
+ * (e.g.: "RECORD", "ENTITY", or
+ * "REPORT") followed by zero or more key components that more
+ * specifically identify the resource within the type of resource (e.g.: an
+ * entity ID or a data source code followed by a record ID).
+ *
+ * @param resourceType The type of resource being identified.
+ * @param components Zero or more key components that more specifically
+ * identify the resource.
+ * @throws NullPointerException If the resource type is null.
+ */
+ public ResourceKey(String resourceType, String... components) {
+ Objects.requireNonNull(resourceType);
+ this.resourceType = resourceType;
+ this.components = (components == null) ? List.of() : List.of(components);
}
- }
-
- /**
- * Returns the resource type that identifies the type of resource. Examples
- * may be "ENTITY", "RECORD" or
- * "REPORT".
- *
- * @return The resource type that identifies the type of resource.
- */
- public String getResourceType() {
- return this.resourceType;
- }
-
- /**
- * Returns the unmodifiable {@link List} of {@link String} components
- * that more specifically identify the resource being locked within the
- * resource type. The returned {@link List} may be empty, but will not
- * be null; however, elements in the {@link List} may be
- * null.
- *
- * @return The unmodifiable {@link List} of {@link String} components
- * that more specifically identify the resource being locked within
- * the resource type.
- */
- public Listtrue if and only if the specified
- * parameter is a non-null reference to an instance of the same class with
- * an equivalent resource type and all component parts in the same order.
- *
- * @param obj The object to compare with.
- * @return true if the objects are equal, otherwise
- * false.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (this == obj) {
- return true;
- }
- if (this.getClass() != obj.getClass()) {
- return false;
+ /**
+ * Constructs with the {@link String} key identifying the type of resource
+ * (e.g.: "RECORD", "ENTITY", or
+ * "REPORT") followed by zero or more key components that more
+ * specifically identify the resource within the type of resource (e.g.: an
+ * entity ID or a data source code followed by a record ID). This constructor
+ * version converts the one or more {@link Object} component instances to
+ * {@link String} instances.
+ *
+ * @param resourceType The type of resource being identified.
+ * @param components Zero or more key components that more specifically
+ * identify the resource.
+ * @throws NullPointerException If the resource type is null.
+ */
+ public ResourceKey(String resourceType, Object... components) {
+ Objects.requireNonNull(resourceType);
+ this.resourceType = resourceType;
+ if (components == null) {
+ this.components = List.of();
+ } else {
+ this.components = new ArrayList<>(components.length);
+ for (Object comp : components) {
+ this.components.add(String.valueOf(comp));
+ }
+ this.components = Collections.unmodifiableList(this.components);
+ }
}
- ResourceKey key = (ResourceKey) obj;
- return Objects.equals(this.getResourceType(), key.getResourceType())
- && Objects.equals(this.getComponents(), key.getComponents());
- }
- /**
- * Overridden to return a result consistent with the {@link #equals(Object)}
- * method that orders resource keys first by their resource types and then
- * by their component identifying parts according to {@link
- * String#compareTo(String)}.
- *
- * @param key The {@link ResourceKey} to compare with.
- * @return A negative number is less-than, zero (0) if equal and a positive
- * number if greater than the specified instance.
- */
- @Override
- public int compareTo(ResourceKey key) {
- int diff = this.getResourceType().compareTo(key.getResourceType());
- if (diff != 0) {
- return diff;
+ /**
+ * Returns the resource type that identifies the type of resource. Examples may
+ * be "ENTITY", "RECORD" or "REPORT".
+ *
+ * @return The resource type that identifies the type of resource.
+ */
+ public String getResourceType() {
+ return this.resourceType;
}
- Listnull; however, elements in the {@link List} may be
+ * null.
+ *
+ * @return The unmodifiable {@link List} of {@link String} components
+ * that more specifically identify the resource being locked within the
+ * resource type.
+ */
+ public Listtrue if and only if the specified parameter
+ * is a non-null reference to an instance of the same class with an equivalent
+ * resource type and all component parts in the same order.
+ *
+ * @param obj The object to compare with.
+ * @return true if the objects are equal, otherwise
+ * false.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ ResourceKey key = (ResourceKey) obj;
+ return Objects.equals(this.getResourceType(), key.getResourceType())
+ && Objects.equals(this.getComponents(), key.getComponents());
}
- }
- /**
- * Parses the encoded text as a {@link ResourceKey}. The encoding is done
- * by the {@link #toString()} method. This method returns null
- * if the specified parameter is null.
- *
- * @param text The text to parse.
- *
- * @return The {@link ResourceKey} parsed from the specified text, or
- * null if the specified parameter is null.
- * @throws IllegalArgumentException If the specified parameter is an empty
- * string after trimming leading and trailing
- * white space.
- */
- public static ResourceKey parse(String text)
- throws IllegalArgumentException {
- // return null if parameter is null
- if (text == null) {
- return null;
+ /**
+ * Overridden to return a result consistent with the {@link #equals(Object)}
+ * method that orders resource keys first by their resource types and then by
+ * their component identifying parts according to
+ * {@link String#compareTo(String)}.
+ *
+ * @param key The {@link ResourceKey} to compare with.
+ * @return A negative number is less-than, zero (0) if equal and a positive
+ * number if greater than the specified instance.
+ */
+ @Override
+ public int compareTo(ResourceKey key) {
+ int diff = this.getResourceType().compareTo(key.getResourceType());
+ if (diff != 0) {
+ return diff;
+ }
+ Listnull if the
+ * specified parameter is null.
+ *
+ * @param text The text to parse.
+ *
+ * @return The {@link ResourceKey} parsed from the specified text, or
+ * null if the specified parameter is null.
+ * @throws IllegalArgumentException If the specified parameter is an empty
+ * string after trimming leading and trailing
+ * white space.
+ */
+ public static ResourceKey parse(String text) throws IllegalArgumentException {
+ // return null if parameter is null
+ if (text == null) {
+ return null;
+ }
- } catch (UnsupportedEncodingException cannotHappen) {
- throw new IllegalStateException("UTF-8 supporting is not supported.");
- }
+ // trim leading and trailing whitespace
+ text = text.trim();
+ if (text.length() == 0) {
+ throw new IllegalArgumentException("The specified text cannot be an empty string or only whitespace.");
+ }
- }
+ String[] tokens = text.split(":");
+ try {
+ String resourceType = URLDecoder.decode(tokens[0], UTF_8);
+ String[] components = new String[tokens.length - 1];
+ for (int index = 1; index < tokens.length; index++) {
+ components[index - 1] = URLDecoder.decode(tokens[index], UTF_8);
+ }
+ return new ResourceKey(resourceType, components);
+
+ } catch (UnsupportedEncodingException cannotHappen) {
+ throw new IllegalStateException("UTF-8 supporting is not supported.");
+ }
+
+ }
- /**
- * Simple test main that constructs a {@link ResourceKey} and converts it to
- * a {@link String}.
- *
- * @param args The command-line arguments.
- */
- public static void main(String[] args) {
- try {
- if (args.length == 0) {
- System.err.println("Required parameters: true or false.
- */
- public static final String CLEAN_DATABASE_KEY = "cleanDatabase";
-
- /**
- * The initialization parameter key for obtaining the {@link
- * ConnectionProvider} to use for connecting to the database from the
- * {@link ConnectionProvider#REGISTRY}.
- */
- public static final String CONNECTION_PROVIDER_KEY = "connectionProvider";
-
- /**
- * The number of expired follow-up tasks.
- */
- private long totalExpiredFollowUpTaskCount = 0L;
-
- /**
- * The {@link ConnectionProvider} to use.
- */
- private ConnectionProvider connectionProvider;
-
- /**
- * The {@link DatabaseType} for this instance.
- */
- private DatabaseType databaseType = null;
-
- /**
- * Default constructor.
- */
- protected AbstractSQLSchedulingService() {
- // do nothing
- }
-
- /**
- * Gets a JDBC {@link Connection} to use. Typically these are obtained from
- * a backing pool so repeated calls to this function without closing the
- * previously obtained {@link Connection} instances could exhaust the pool.
- * This may block until a {@link Connection} is available.
- *
- * @return The {@link Connection} that was obtained.
- *
- * @throws SQLException If a JDBC failure occurs.
- */
- protected Connection getConnection() throws SQLException {
- return this.connectionProvider.getConnection();
- }
-
- /**
- * Overridden to obtain the {@link ConnectionProvider}.
- *
- * {@inheritDoc}
- *
- * @param config The {@link JsonObject} describing the configuration.
- */
- protected void doInit(JsonObject config)
- throws ServiceSetupException
- {
- try {
- Boolean clean = getConfigBoolean(config, CLEAN_DATABASE_KEY, FALSE);
-
- String providerKey = getConfigString(config,
- CONNECTION_PROVIDER_KEY,
- true);
-
-
- try {
- this.connectionProvider = ConnectionProvider.REGISTRY.lookup(providerKey);
- } catch (NameNotFoundException e) {
- throw new ServiceSetupException(
- "No ConnectionProvider was registered to the name specified by the "
- + "\"" + CONNECTION_PROVIDER_KEY + "\" initialization parameter: "
- + providerKey);
- }
-
- // set the database type
- this.databaseType = this.initDatabaseType();
-
- // ensure the schema exists
- this.ensureSchema(clean);
-
- } catch (SQLException e) {
- throw new ServiceSetupException(
- "Failed to connect to database or initialize schema.", e);
+public abstract class AbstractSQLSchedulingService extends AbstractSchedulingService {
+ /**
+ * The {@link Calendar} to use for retrieving timestamps from the database.
+ */
+ private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+
+ /**
+ * The initialization parameter key for checking if the persistent store of
+ * follow-up tasks should be dropped / deleted and recreated during
+ * initialization. Values should be true or false.
+ */
+ public static final String CLEAN_DATABASE_KEY = "cleanDatabase";
+
+ /**
+ * The initialization parameter key for obtaining the {@link ConnectionProvider}
+ * to use for connecting to the database from the
+ * {@link ConnectionProvider#REGISTRY}.
+ */
+ public static final String CONNECTION_PROVIDER_KEY = "connectionProvider";
+
+ /**
+ * The number of expired follow-up tasks.
+ */
+ private long totalExpiredFollowUpTaskCount = 0L;
+
+ /**
+ * The {@link ConnectionProvider} to use.
+ */
+ private ConnectionProvider connectionProvider;
+
+ /**
+ * The {@link DatabaseType} for this instance.
+ */
+ private DatabaseType databaseType = null;
+
+ /**
+ * Default constructor.
+ */
+ protected AbstractSQLSchedulingService() {
+ // do nothing
}
- }
-
- /**
- * Initializes the {@link DatabaseType} to use for formatting SQL statements.
- *
- * @return The {@link DatabaseType} to use.
- *
- * @throws SQLException If a failure occurs.
- */
- protected DatabaseType initDatabaseType() throws SQLException {
- Connection conn = this.getConnection();
- try {
- return DatabaseType.detect(conn);
- } finally {
- conn = close(conn);
- }
- }
-
- /**
- * Gets the {@link DatabaseType} used by this instance.
- *
- * @return The {@link DatabaseType} used by this instance.
- */
- public DatabaseType getDatabaseType() {
- return this.databaseType;
- }
-
- /**
- * Ensures the schema exists and alternatively drops the existing the schema
- * and recreates it. This is called from {@link #doInit(JsonObject)}.
- *
- * @param recreate true if the existing schema should be
- * dropped, otherwise false.
- *
- * @throws SQLException If a failure occurs.
- */
- protected abstract void ensureSchema(boolean recreate) throws SQLException;
-
- /**
- * Overridden to do nothing.
- */
- @Override
- protected void doDestroy() {
- // do nothing
- }
-
- /**
- * Implemented to save the specified follow-up tasks to the backing SQLite
- * persistent store.
- */
- @Override
- protected synchronized void enqueueFollowUpTask(Task task)
- throws ServiceExecutionException
- {
- Connection conn = null;
- boolean success = false;
- try {
- // obtain the connection
- conn = this.getConnection();
-
- // update the multiplicity if the row exists
- boolean updated = this.incrementFollowUpMultiplicity(conn, task);
-
- // check if we updated a row
- if (!updated) {
- // insert a new row since none was updated
- this.insertNewFollowUpTask(conn, task);
- }
-
- // commit the connection
- conn.commit();
- success = true;
-
- } catch (SQLException e) {
- e.printStackTrace();
- throw new ServiceExecutionException("JDBC failure occurred", e);
-
- } finally {
- if (!success) {
- rollback(conn);
- }
- conn = close(conn);
- }
- }
-
- /**
- * Increments the multiplicity for the specified follow-up task in the
- * database by updating the associated row if it exists. This
- * true if the row existed and was updated, otherwise
- * false
- *
- * @param conn The {@link Connection} to use to connect to the database.
- * @param task The {@link Task} describing the row to update.
- * @return true if the row existed and was updated, otherwise
- * false.
- * @throws SQLException If a JDBC failure occurs.
- */
- protected boolean incrementFollowUpMultiplicity(Connection conn, Task task)
- throws SQLException
- {
- PreparedStatement ps = null;
- try {
- // prepare the statement
- ps = conn.prepareStatement(
- "UPDATE sz_follow_up_tasks "
- + "SET multiplicity = multiplicity + 1 "
- + "WHERE signature = ? "
- + "AND allow_collapse_flag = 1 "
- + "AND expire_lease_at IS NULL "
- + "AND task_id = (SELECT MAX(task_id) FROM sz_follow_up_tasks "
- + "WHERE signature = ? AND allow_collapse_flag = 1 "
- + "AND expire_lease_at IS NULL)");
-
- ps.setString(1, task.getSignature());
- ps.setString(2, task.getSignature());
-
- int rowCount = ps.executeUpdate();
-
- if (rowCount == 0) {
- return false;
- } else if (rowCount == 1) {
- return true;
- } else {
- logError("MULTIPLE ROWS UPDATED FOR FOLLOW-UP TASK: ", task);
- throw new IllegalStateException(
- "Somehow updated multiple rows when updating task multiplicity. "
- + "task=[ " + task + " ]");
- }
-
- } finally {
- ps = close(ps);
- }
- }
-
- /**
- * Inserts a new follow-up task in the database schema.
- *
- * @param conn The {@link Connection} to use to connect to the database.
- * @param task The {@link Task} describing the row to insert.
- * @throws SQLException If a JDBC failure occurs.
- */
- protected void insertNewFollowUpTask(Connection conn, Task task)
- throws SQLException
- {
- PreparedStatement ps = null;
- try {
- ps = conn.prepareStatement(
- "INSERT INTO sz_follow_up_tasks ("
- + "signature, allow_collapse_flag, json_text) VALUES (?, ?, ?)");
- ps.setString(1, task.getSignature());
- ps.setInt(2, (task.isAllowingCollapse() ? 1 : 0));
- ps.setString(3, task.toJsonText());
-
- int rowCount = ps.executeUpdate();
-
- if (rowCount != 1) {
- throw new SQLException(
- "Unexpected row count on insert: " + rowCount);
- }
- } finally {
- ps = close(ps);
- }
- }
-
- /**
- * This message can be used for debugging to dump the contents of the
- * follow-up table to standard error.
- *
- * @throws SQLException If a JDBC failure occurs.
- */
- protected void dumpFollowUpTable() throws SQLException {
- Connection conn = null;
- PreparedStatement ps = null;
- ResultSet rs = null;
- try {
- conn = this.getConnection();
-
- StringBuilder sb = new StringBuilder();
- sb.append("SELECT task_id, json_text, signature, multiplicity, ");
- sb.append("lease_id, expire_lease_at, modified_on, created_on ");
- sb.append("FROM sz_follow_up_tasks ");
-
- ps = conn.prepareStatement(sb.toString());
-
- rs = ps.executeQuery();
-
- long now = System.currentTimeMillis();
- long delayTime = now - this.getFollowUpDelay();
- long timeoutTime = now - this.getFollowUpTimeout();
-
- System.err.println();
- System.err.println("-------------------------------------------------");
- while (rs.next()) {
- System.err.println(
- rs.getLong(1) + " / " + rs.getString(2)
- + " / " + rs.getInt(4)
- + " / " + rs.getString(5)
- + " / " + rs.getTimestamp(6, UTC_CALENDAR)
- + " / " + rs.getTimestamp(7, UTC_CALENDAR)
- + " vs " + (new Timestamp(delayTime))
- + " / " + rs.getTimestamp(8, UTC_CALENDAR)
- + " vs " + (new Timestamp(timeoutTime)));
- }
- rs = close(rs);
- ps = close(ps);
-
- } catch (SQLException e) {
- e.printStackTrace();
- throw e;
-
- } finally {
- rs = close(rs);
- ps = close(ps);
- conn = close(conn);
+
+ /**
+ * Gets a JDBC {@link Connection} to use. Typically these are obtained from a
+ * backing pool so repeated calls to this function without closing the
+ * previously obtained {@link Connection} instances could exhaust the pool. This
+ * may block until a {@link Connection} is available.
+ *
+ * @return The {@link Connection} that was obtained.
+ *
+ * @throws SQLException If a JDBC failure occurs.
+ */
+ protected Connection getConnection() throws SQLException {
+ return this.connectionProvider.getConnection();
}
- }
-
- /**
- * Fetches at most the specified number of follow-up tasks from the backing
- * SQLite persistent store.
- *
- * @param count The suggested number of follow-up tasks to retrieve from
- * persistent storage.
- *
- * @return The {@link List} of dequeued follow-up tasks.
- *
- * @throws ServiceExecutionException If a failure occurs.
- */
- @Override
- protected synchronized Listtrue if the existing schema should be dropped,
+ * otherwise false.
+ *
+ * @throws SQLException If a failure occurs.
+ */
+ protected abstract void ensureSchema(boolean recreate) throws SQLException;
+
+ /**
+ * Overridden to do nothing.
+ */
+ @Override
+ protected void doDestroy() {
+ // do nothing
+ }
- // check if no rows were updated
- if (leasedCount == 0) {
- return new ArrayList<>(0);
- }
+ /**
+ * Implemented to save the specified follow-up tasks to the backing SQLite
+ * persistent store.
+ */
+ @Override
+ protected synchronized void enqueueFollowUpTask(Task task) throws ServiceExecutionException {
+ Connection conn = null;
+ boolean success = false;
+ try {
+ // obtain the connection
+ conn = this.getConnection();
+
+ // update the multiplicity if the row exists
+ boolean updated = this.incrementFollowUpMultiplicity(conn, task);
+
+ // check if we updated a row
+ if (!updated) {
+ // insert a new row since none was updated
+ this.insertNewFollowUpTask(conn, task);
+ }
+
+ // commit the connection
+ conn.commit();
+ success = true;
+
+ } catch (SQLException e) {
+ System.err.println(e.getMessage());
+ System.err.println(formatStackTrace(e.getStackTrace()));
+ throw new ServiceExecutionException("JDBC failure occurred", e);
+
+ } finally {
+ if (!success) {
+ rollback(conn);
+ }
+ conn = close(conn);
+ }
+ }
- // now get the leased rows
- Listtrue if the
+ * row existed and was updated, otherwise false
+ *
+ * @param conn The {@link Connection} to use to connect to the database.
+ * @param task The {@link Task} describing the row to update.
+ * @return true if the row existed and was updated, otherwise
+ * false.
+ * @throws SQLException If a JDBC failure occurs.
+ */
+ protected boolean incrementFollowUpMultiplicity(Connection conn, Task task) throws SQLException {
+ PreparedStatement ps = null;
+ try {
+ // prepare the statement
+ ps = conn.prepareStatement("UPDATE sz_follow_up_tasks " + "SET multiplicity = multiplicity + 1 "
+ + "WHERE signature = ? " + "AND allow_collapse_flag = 1 " + "AND expire_lease_at IS NULL "
+ + "AND task_id = (SELECT MAX(task_id) FROM sz_follow_up_tasks "
+ + "WHERE signature = ? AND allow_collapse_flag = 1 " + "AND expire_lease_at IS NULL)");
+
+ ps.setString(1, task.getSignature());
+ ps.setString(2, task.getSignature());
+
+ int rowCount = ps.executeUpdate();
+
+ if (rowCount == 0) {
+ return false;
+ } else if (rowCount == 1) {
+ return true;
+ } else {
+ logError("MULTIPLE ROWS UPDATED FOR FOLLOW-UP TASK: ", task);
+ throw new IllegalStateException(
+ "Somehow updated multiple rows when updating task multiplicity. " + "task=[ " + task + " ]");
+ }
+
+ } finally {
+ ps = close(ps);
+ }
+ }
- // commit the transaction
- conn.commit();
- success = true;
+ /**
+ * Inserts a new follow-up task in the database schema.
+ *
+ * @param conn The {@link Connection} to use to connect to the database.
+ * @param task The {@link Task} describing the row to insert.
+ * @throws SQLException If a JDBC failure occurs.
+ */
+ protected void insertNewFollowUpTask(Connection conn, Task task) throws SQLException {
+ PreparedStatement ps = null;
+ try {
+ ps = conn.prepareStatement(
+ "INSERT INTO sz_follow_up_tasks (" + "signature, allow_collapse_flag, json_text) VALUES (?, ?, ?)");
+ ps.setString(1, task.getSignature());
+ ps.setInt(2, (task.isAllowingCollapse() ? 1 : 0));
+ ps.setString(3, task.toJsonText());
+
+ int rowCount = ps.executeUpdate();
+
+ if (rowCount != 1) {
+ throw new SQLException("Unexpected row count on insert: " + rowCount);
+ }
+ } finally {
+ ps = close(ps);
+ }
+ }
- // return the result list
- return result;
+ /**
+ * This message can be used for debugging to dump the contents of the follow-up
+ * table to standard error.
+ *
+ * @throws SQLException If a JDBC failure occurs.
+ */
+ protected void dumpFollowUpTable() throws SQLException {
+ Connection conn = null;
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+ try {
+ conn = this.getConnection();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT task_id, json_text, signature, multiplicity, ");
+ sb.append("lease_id, expire_lease_at, modified_on, created_on ");
+ sb.append("FROM sz_follow_up_tasks ");
+
+ ps = conn.prepareStatement(sb.toString());
+
+ rs = ps.executeQuery();
+
+ long now = System.currentTimeMillis();
+ long delayTime = now - this.getFollowUpDelay();
+ long timeoutTime = now - this.getFollowUpTimeout();
+
+ System.err.println();
+ System.err.println("-------------------------------------------------");
+ while (rs.next()) {
+ System.err.println(rs.getLong(1) + " / " + rs.getString(2) + " / " + rs.getInt(4) + " / "
+ + rs.getString(5) + " / " + rs.getTimestamp(6, UTC_CALENDAR) + " / "
+ + rs.getTimestamp(7, UTC_CALENDAR) + " vs " + (new Timestamp(delayTime)) + " / "
+ + rs.getTimestamp(8, UTC_CALENDAR) + " vs " + (new Timestamp(timeoutTime)));
+ }
+ rs = close(rs);
+ ps = close(ps);
+
+ } catch (SQLException e) {
+ System.err.println(e.getMessage());
+ System.err.println(formatStackTrace(e.getStackTrace()));
+ throw e;
+
+ } finally {
+ rs = close(rs);
+ ps = close(ps);
+ conn = close(conn);
+ }
+ }
- } catch (SQLException e) {
- throw new ServiceExecutionException(
- "Failed to dequeue follow-up task", e);
+ /**
+ * Fetches at most the specified number of follow-up tasks from the backing
+ * SQLite persistent store.
+ *
+ * @param count The suggested number of follow-up tasks to retrieve from
+ * persistent storage.
+ *
+ * @return The {@link List} of dequeued follow-up tasks.
+ *
+ * @throws ServiceExecutionException If a failure occurs.
+ */
+ @Override
+ protected synchronized Listtrue if a follow-up was deleted and false
- * if not (usually because it was already deleted).
- * @throws SQLException If a JDBC failure occurs.
- */
- protected boolean deleteFollowUpTask(Connection conn,
- ScheduledTask task)
- throws SQLException
- {
- PreparedStatement ps = null;
- try {
- ps = conn.prepareStatement(
- "DELETE FROM sz_follow_up_tasks WHERE task_id = ?");
-
- String followUpId = task.getFollowUpId();
- int index = followUpId.indexOf(":");
- long taskId = Long.parseLong(followUpId.substring(0, index));
-
- ps.setLong(1, taskId);
-
- int rowCount = ps.executeUpdate();
-
- if (rowCount > 1) {
- throw new SQLException(
- "Multiple follow-up rows deleted when one was expected: "
- + rowCount);
- }
-
- return (rowCount == 1);
-
- } finally {
- ps = close(ps);
+
+ /**
+ * Deletes a follow-up task (typically once it hqs been completed). This is
+ * called from the default {@link #completeFollowUpTask(ScheduledTask)}
+ * implementation.
+ *
+ * @param conn The {@link Connection} to use.
+ * @param task The {@link ScheduledTask} describing the task to delete.
+ * @return true if a follow-up was deleted and false
+ * if not (usually because it was already deleted).
+ * @throws SQLException If a JDBC failure occurs.
+ */
+ protected boolean deleteFollowUpTask(Connection conn, ScheduledTask task) throws SQLException {
+ PreparedStatement ps = null;
+ try {
+ ps = conn.prepareStatement("DELETE FROM sz_follow_up_tasks WHERE task_id = ?");
+
+ String followUpId = task.getFollowUpId();
+ int index = followUpId.indexOf(":");
+ long taskId = Long.parseLong(followUpId.substring(0, index));
+
+ ps.setLong(1, taskId);
+
+ int rowCount = ps.executeUpdate();
+
+ if (rowCount > 1) {
+ throw new SQLException("Multiple follow-up rows deleted when one was expected: " + rowCount);
+ }
+
+ return (rowCount == 1);
+
+ } finally {
+ ps = close(ps);
+ }
}
- }
-
- /**
- * Gets the total number of follow-up tasks that were dequeued and expired
- * before being handled.
- *
- * @return The total number of follow-up tasks that were dequeued and expired
- * before being handled.
- */
- public long getTotalExpiredFollowUpTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.totalExpiredFollowUpTaskCount;
+
+ /**
+ * Gets the total number of follow-up tasks that were dequeued and expired
+ * before being handled.
+ *
+ * @return The total number of follow-up tasks that were dequeued and expired
+ * before being handled.
+ */
+ public long getTotalExpiredFollowUpTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.totalExpiredFollowUpTaskCount;
+ }
}
- }
}
diff --git a/src/main/java/com/senzing/listener/service/scheduling/AbstractSchedulingService.java b/src/main/java/com/senzing/listener/service/scheduling/AbstractSchedulingService.java
index 5b36fa8..5dfe0a3 100644
--- a/src/main/java/com/senzing/listener/service/scheduling/AbstractSchedulingService.java
+++ b/src/main/java/com/senzing/listener/service/scheduling/AbstractSchedulingService.java
@@ -28,3726 +28,3610 @@
* Provides an abstract base class for implementing {@link SchedulingService}.
*/
public abstract class AbstractSchedulingService implements SchedulingService {
- /**
- * The number of milliseconds to wait for the task handler to be ready.
- */
- private static final long READY_TIMEOUT = 2000L;
-
- /**
- * Constant for nanosecond/millisecond conversion.
- */
- private static final long ONE_MILLION = 1000000L;
-
- /**
- * The default concurrency. The default is to serialize task handling in
- * a single thread.
- */
- public static final int DEFAULT_CONCURRENCY = 1;
-
- /**
- * The default number of milliseconds for the {@link #POSTPONED_TIMEOUT_KEY}
- * initialization parameter.
- */
- public static final long DEFAULT_POSTPONED_TIMEOUT = 50L;
-
- /**
- * The default number of milliseconds for the {@link #STANDARD_TIMEOUT_KEY}
- * initialization parameter if not otherwise specified.
- */
- public static final long DEFAULT_STANDARD_TIMEOUT = 1500L;
-
- /**
- * The default number of milliseconds for the {@link #FOLLOW_UP_DELAY_KEY}
- * initialization parameter if not otherwise specified.
- */
- public static final long DEFAULT_FOLLOW_UP_DELAY = 10000L;
-
- /**
- * The default maximum number of milliseconds for the {@link
- * #FOLLOW_UP_TIMEOUT_KEY} initialization parameter if not otherwise
- * specified.
- */
- public static final long DEFAULT_FOLLOW_UP_TIMEOUT = 60000L;
-
- /**
- * The default number of follow-up tasks to fetch from persistent storage at
- * a time.
- */
- public static final int DEFAULT_FOLLOW_UP_FETCH = 100;
-
- /**
- * The config property key for configuring the concurrency.
- */
- public static final String CONCURRENCY_KEY = "concurrency";
-
- /**
- * The initialization parameter to specify the number of milliseconds to
- * sleep between checks on the locks required for tasks that have been
- * postponed due to contention. If not configured then the value is set to
- * {@link #DEFAULT_POSTPONED_TIMEOUT}. If the value is specified it should
- * be non-negative.
- */
- public static final String POSTPONED_TIMEOUT_KEY = "postponedTimeout";
-
- /**
- * The initialization parameter to specify the number of milliseconds to
- * sleep between checking to see if task handling should cease. This
- * timeout is used when there are no postponed tasks due to contention.
- * If not configured then the value is set to {@link
- * #DEFAULT_STANDARD_TIMEOUT}. If the value is specified it should be
- * non-negative.
- */
- public static final String STANDARD_TIMEOUT_KEY = "standardTimeout";
-
- /**
- * The initialization parameter to specify the number of milliseconds to
- * delay before attempting to execute a follow-up task. This delay is
- * used to give the opportunity to receive duplicate follow-up tasks that
- * can be collapsed. Whenever a duplicate is collapsed, the delay timer
- * starts over unless the {@linkplain #FOLLOW_UP_TIMEOUT_KEY maximum
- * follow-up deferral time} has been reached. If not configured then
- * the value is set to {@link #DEFAULT_FOLLOW_UP_DELAY}. If the value is
- * specified it should be non-negative.
- */
- public static final String FOLLOW_UP_DELAY_KEY = "followUpDelay";
-
- /**
- * The initialization parameter to specify the maximum number of milliseconds
- * to defer a follow-up task. Once a follow-up task has been deferred this
- * number of milliseconds it will no longer be purposely delayed to wait for
- * additional duplicates to be scheduled and collapsed. This is also the
- * amount of time used to cache a follow-up task from persistent storage
- * before considering the cached version expired and make it available again
- * from persistent storage. If not configured then the value is set to {@link
- * #DEFAULT_FOLLOW_UP_TIMEOUT}. If the value is specified it should be
- * non-negative and must be greater than the delay time specified by
- * {@link #FOLLOW_UP_DELAY_KEY}.
- */
- public static final String FOLLOW_UP_TIMEOUT_KEY = "followUpTimeout";
-
- /**
- * The initialization parameter to specify the maximum number of follow-up
- * tasks to retrieve from persistent storage at a time to refill the
- * in-memory cache. The retrieved tasks should not be returned from
- * persistent storage again until after the {@linkplain #FOLLOW_UP_TIMEOUT_KEY
- * follow-up timeout} has elapsed and after it has elapsed, the in-memory
- * cache should be considered expired. If not configured then the value is
- * to {@link #DEFAULT_FOLLOW_UP_FETCH}. If the value is specified it should
- * be a positive number.
- */
- public static final String FOLLOW_UP_FETCH_KEY = "followUpFetch";
-
- /**
- * The initialization parameter used by the default implementation of
- * {@link #initLockingService(JsonObject)} to specify the Java class name
- * of the {@link LockingService} to use. If the default implementation of
- * {@link #initLockingService(JsonObject)} is overridden, then this key may
- * have no effect in the derived implementation.
- */
- public static final String LOCKING_SERVICE_CLASS_KEY = "lockingService";
-
- /**
- * The default value for the {@link #LOCKING_SERVICE_CLASS_KEY} if the value
- * is not specified. This is the class name for {@link
- * ProcessScopeLockingService}.
- */
- public static final String DEFAULT_LOCKING_SERVICE_CLASS_NAME = ProcessScopeLockingService.class.getName();
-
- /**
- * The initialization parameter referencing a JSON object or {@link String}
- * that represents the configuration for the {@link LockingService} instance
- * created by the default implementation of {@link
- * #initLockingService(JsonObject)} using the {@link
- * #LOCKING_SERVICE_CLASS_KEY} init parameter. If the default implementation
- * of {@link #initLockingService(JsonObject)} is overridden, then this key may
- * have no effect in the derived implementation.
- */
- public static final String LOCKING_SERVICE_CONFIG_KEY = "lockingServiceConfig";
-
- /**
- * Millisecond units constant for {@link Stat} instances.
- */
- private static final String MILLISECOND_UNITS = "ms";
-
- /**
- * Thread units constant for {@link Stat} instances.
- */
- private static final String THREAD_UNITS = "threads";
-
- /**
- * Task units constant for {@link Stat} instances.
- */
- private static final String TASK_UNITS = "tasks";
-
- /**
- * Task group units constant for {@link Stat} instances.
- */
- private static final String TASK_GROUP_UNITS = "task groups";
-
- /**
- * Call units constant for {@link Stat} instances.
- */
- private static final String CALL_UNITS = "calls";
-
- /**
- * Tasks per call units constant for {@link Stat} instances.
- */
- private static final String TASKS_PER_CALL_UNITS = "tasks per call";
-
- /**
- * Enumerates the various task types.
- */
- private enum TaskType {
/**
- * Pending tasks that have never been handled.
+ * The number of milliseconds to wait for the task handler to be ready.
*/
- PENDING,
+ private static final long READY_TIMEOUT = 2000L;
/**
- * Postponed tasks that were previously attempted but postponed.
+ * Constant for nanosecond/millisecond conversion.
*/
- POSTPONED,
+ private static final long ONE_MILLION = 1000000L;
/**
- * Follow-up tasks that were scheduled in response to previous tasks.
+ * The default concurrency. The default is to serialize task handling in a
+ * single thread.
*/
- FOLLOW_UP;
- }
+ public static final int DEFAULT_CONCURRENCY = 1;
- /**
- * The various keys used for timing operations.
- */
- public enum Stat implements Statistic {
/**
- * The number of worker threads used to asynchronously handle the tasks.
+ * The default number of milliseconds for the {@link #POSTPONED_TIMEOUT_KEY}
+ * initialization parameter.
*/
- concurrency(THREAD_UNITS),
+ public static final long DEFAULT_POSTPONED_TIMEOUT = 50L;
/**
- * The timeout to use when waiting for new tasks to show up when there
- * are no postponed tasks. When there are postponed tasks then
- * {@link #postponedTimeout} is used.
+ * The default number of milliseconds for the {@link #STANDARD_TIMEOUT_KEY}
+ * initialization parameter if not otherwise specified.
*/
- standardTimeout(MILLISECOND_UNITS),
+ public static final long DEFAULT_STANDARD_TIMEOUT = 1500L;
/**
- * The number of milliseconds to sleep between checks on the locks required
- * for tasks that have been postponed due to contention.
+ * The default number of milliseconds for the {@link #FOLLOW_UP_DELAY_KEY}
+ * initialization parameter if not otherwise specified.
*/
- postponedTimeout(MILLISECOND_UNITS),
+ public static final long DEFAULT_FOLLOW_UP_DELAY = 10000L;
/**
- * The number of milliseconds to delay a follow-up task initially (to allow
- * duplicates to be collapsed with it) and after each time a duplicate is
- * found. The total deferral of the follow-up task is governed by the
- * {@link #followUpTimeout} value.
+ * The default maximum number of milliseconds for the
+ * {@link #FOLLOW_UP_TIMEOUT_KEY} initialization parameter if not otherwise
+ * specified.
*/
- followUpDelay(MILLISECOND_UNITS),
+ public static final long DEFAULT_FOLLOW_UP_TIMEOUT = 60000L;
/**
- * The maximum number of milliseconds to defer a follow-up task while
- * waiting for duplicate tasks to be collapsed with it.
+ * The default number of follow-up tasks to fetch from persistent storage at a
+ * time.
*/
- followUpTimeout(MILLISECOND_UNITS),
+ public static final int DEFAULT_FOLLOW_UP_FETCH = 100;
/**
- * The average number of milliseconds from when a non-follow-up task is
- * scheduled until it has been handled.
+ * The config property key for configuring the concurrency.
*/
- averageTaskTime(MILLISECOND_UNITS),
+ public static final String CONCURRENCY_KEY = "concurrency";
/**
- * The average number of milliseconds from when a task group has its first
- * task scheduled until all of its tasks have been handled.
+ * The initialization parameter to specify the number of milliseconds to sleep
+ * between checks on the locks required for tasks that have been postponed due
+ * to contention. If not configured then the value is set to
+ * {@link #DEFAULT_POSTPONED_TIMEOUT}. If the value is specified it should be
+ * non-negative.
*/
- averageTaskGroupTime(MILLISECOND_UNITS),
+ public static final String POSTPONED_TIMEOUT_KEY = "postponedTimeout";
/**
- * The longest amount of time (in milliseconds) for when a non-followup
- * task was scheduled until it was completely processed.
+ * The initialization parameter to specify the number of milliseconds to sleep
+ * between checking to see if task handling should cease. This timeout is used
+ * when there are no postponed tasks due to contention. If not configured then
+ * the value is set to {@link #DEFAULT_STANDARD_TIMEOUT}. If the value is
+ * specified it should be non-negative.
*/
- longestTaskTime(MILLISECOND_UNITS),
+ public static final String STANDARD_TIMEOUT_KEY = "standardTimeout";
/**
- * The longest amount of time (in milliseconds) for when a non-followup
- * task was scheduled until it was completely processed.
+ * The initialization parameter to specify the number of milliseconds to delay
+ * before attempting to execute a follow-up task. This delay is used to give the
+ * opportunity to receive duplicate follow-up tasks that can be collapsed.
+ * Whenever a duplicate is collapsed, the delay timer starts over unless the
+ * {@linkplain #FOLLOW_UP_TIMEOUT_KEY maximum follow-up deferral time} has been
+ * reached. If not configured then the value is set to
+ * {@link #DEFAULT_FOLLOW_UP_DELAY}. If the value is specified it should be
+ * non-negative.
*/
- longestTaskGroupTime(MILLISECOND_UNITS),
+ public static final String FOLLOW_UP_DELAY_KEY = "followUpDelay";
/**
- * The number of non-follow-up tasks that have made the round trip from
- * being scheduled to the point where they are completely handled.
+ * The initialization parameter to specify the maximum number of milliseconds to
+ * defer a follow-up task. Once a follow-up task has been deferred this number
+ * of milliseconds it will no longer be purposely delayed to wait for additional
+ * duplicates to be scheduled and collapsed. This is also the amount of time
+ * used to cache a follow-up task from persistent storage before considering the
+ * cached version expired and make it available again from persistent storage.
+ * If not configured then the value is set to
+ * {@link #DEFAULT_FOLLOW_UP_TIMEOUT}. If the value is specified it should be
+ * non-negative and must be greater than the delay time specified by
+ * {@link #FOLLOW_UP_DELAY_KEY}.
*/
- taskCompleteCount(TASK_UNITS),
+ public static final String FOLLOW_UP_TIMEOUT_KEY = "followUpTimeout";
/**
- * The number of non-follow-up tasks that have been completed successfully.
+ * The initialization parameter to specify the maximum number of follow-up tasks
+ * to retrieve from persistent storage at a time to refill the in-memory cache.
+ * The retrieved tasks should not be returned from persistent storage again
+ * until after the {@linkplain #FOLLOW_UP_TIMEOUT_KEY follow-up timeout} has
+ * elapsed and after it has elapsed, the in-memory cache should be considered
+ * expired. If not configured then the value is to
+ * {@link #DEFAULT_FOLLOW_UP_FETCH}. If the value is specified it should be a
+ * positive number.
*/
- taskSuccessCount(TASK_UNITS),
+ public static final String FOLLOW_UP_FETCH_KEY = "followUpFetch";
/**
- * The number of non-follow-up tasks that have been completed with a
- * failure.
+ * The initialization parameter used by the default implementation of
+ * {@link #initLockingService(JsonObject)} to specify the Java class name of the
+ * {@link LockingService} to use. If the default implementation of
+ * {@link #initLockingService(JsonObject)} is overridden, then this key may have
+ * no effect in the derived implementation.
*/
- taskFailureCount(TASK_UNITS),
+ public static final String LOCKING_SERVICE_CLASS_KEY = "lockingService";
/**
- * The number of non-follow-up tasks that have been aborted.
+ * The default value for the {@link #LOCKING_SERVICE_CLASS_KEY} if the value is
+ * not specified. This is the class name for {@link ProcessScopeLockingService}.
*/
- taskAbortCount(TASK_UNITS),
+ public static final String DEFAULT_LOCKING_SERVICE_CLASS_NAME = ProcessScopeLockingService.class.getName();
/**
- * The number of follow-up tasks that have made the round trip from
- * being scheduled to the point where they are completely handled.
+ * The initialization parameter referencing a JSON object or {@link String} that
+ * represents the configuration for the {@link LockingService} instance created
+ * by the default implementation of {@link #initLockingService(JsonObject)}
+ * using the {@link #LOCKING_SERVICE_CLASS_KEY} init parameter. If the default
+ * implementation of {@link #initLockingService(JsonObject)} is overridden, then
+ * this key may have no effect in the derived implementation.
*/
- followUpCompleteCount(TASK_UNITS),
+ public static final String LOCKING_SERVICE_CONFIG_KEY = "lockingServiceConfig";
/**
- * The number of follow-up tasks that have been completed successfully.
+ * Millisecond units constant for {@link Stat} instances.
*/
- followUpSuccessCount(TASK_UNITS),
+ private static final String MILLISECOND_UNITS = "ms";
/**
- * The number of follow-up tasks that have been completed with a failure.
+ * Thread units constant for {@link Stat} instances.
*/
- followUpFailureCount(TASK_UNITS),
+ private static final String THREAD_UNITS = "threads";
/**
- * The average number of milliseconds for a task to be handled by the
- * {@link TaskHandler} via {@link
- * TaskHandler#handleTask(String,Map,int,Scheduler)}.
+ * Task units constant for {@link Stat} instances.
*/
- averageHandleTask(MILLISECOND_UNITS),
+ private static final String TASK_UNITS = "tasks";
/**
- * The number of times the {@link
- * TaskHandler#handleTask(String,Map,int,Scheduler)} method has been called
- * to handle a task (follow-up or not).
+ * Task group units constant for {@link Stat} instances.
*/
- handleTaskCount(CALL_UNITS),
+ private static final String TASK_GROUP_UNITS = "task groups";
/**
- * The number of times that the {@link
- * TaskHandler#handleTask(String,Map,int,Scheduler)} has been called
- * successfully (i.e.: without any exceptions) to handle a task (follow-up
- * or not).
+ * Call units constant for {@link Stat} instances.
*/
- handleTaskSuccessCount(CALL_UNITS),
+ private static final String CALL_UNITS = "calls";
/**
- * The number of times that the {@link
- * TaskHandler#handleTask(String,Map,int,Scheduler)} has been called
- * unsuccessfully (i.e.: with an exceptions being thrown) to handle a task
- * (follow-up or not).
+ * Tasks per call units constant for {@link Stat} instances.
*/
- handleTaskFailureCount(CALL_UNITS),
+ private static final String TASKS_PER_CALL_UNITS = "tasks per call";
/**
- * Gets the ratio of the number of times {@link
- * TaskHandler#handleTask(String,Map,int,Scheduler)} has been called for
- * follow-up tasks to number of times it has been called for all
- * tasks that have been handled.
+ * Enumerates the various task types.
*/
- followUpHandleTaskRatio(null),
+ private enum TaskType {
+ /**
+ * Pending tasks that have never been handled.
+ */
+ PENDING,
+
+ /**
+ * Postponed tasks that were previously attempted but postponed.
+ */
+ POSTPONED,
+
+ /**
+ * Follow-up tasks that were scheduled in response to previous tasks.
+ */
+ FOLLOW_UP;
+ }
/**
- * The number of non-followup tasks that have made the round trip from being
- * scheduled to the point where they are completely handled. Some
- * messages may make the round trip more than once if a failure occurs in
- * processing part or all of the message.
+ * The various keys used for timing operations.
*/
- taskGroupCompleteCount(TASK_GROUP_UNITS),
+ public enum Stat implements Statistic {
+ /**
+ * The number of worker threads used to asynchronously handle the tasks.
+ */
+ concurrency(THREAD_UNITS),
+
+ /**
+ * The timeout to use when waiting for new tasks to show up when there are no
+ * postponed tasks. When there are postponed tasks then
+ * {@link #postponedTimeout} is used.
+ */
+ standardTimeout(MILLISECOND_UNITS),
+
+ /**
+ * The number of milliseconds to sleep between checks on the locks required for
+ * tasks that have been postponed due to contention.
+ */
+ postponedTimeout(MILLISECOND_UNITS),
+
+ /**
+ * The number of milliseconds to delay a follow-up task initially (to allow
+ * duplicates to be collapsed with it) and after each time a duplicate is found.
+ * The total deferral of the follow-up task is governed by the
+ * {@link #followUpTimeout} value.
+ */
+ followUpDelay(MILLISECOND_UNITS),
+
+ /**
+ * The maximum number of milliseconds to defer a follow-up task while waiting
+ * for duplicate tasks to be collapsed with it.
+ */
+ followUpTimeout(MILLISECOND_UNITS),
+
+ /**
+ * The average number of milliseconds from when a non-follow-up task is
+ * scheduled until it has been handled.
+ */
+ averageTaskTime(MILLISECOND_UNITS),
+
+ /**
+ * The average number of milliseconds from when a task group has its first task
+ * scheduled until all of its tasks have been handled.
+ */
+ averageTaskGroupTime(MILLISECOND_UNITS),
+
+ /**
+ * The longest amount of time (in milliseconds) for when a non-followup task was
+ * scheduled until it was completely processed.
+ */
+ longestTaskTime(MILLISECOND_UNITS),
+
+ /**
+ * The longest amount of time (in milliseconds) for when a non-followup task was
+ * scheduled until it was completely processed.
+ */
+ longestTaskGroupTime(MILLISECOND_UNITS),
+
+ /**
+ * The number of non-follow-up tasks that have made the round trip from being
+ * scheduled to the point where they are completely handled.
+ */
+ taskCompleteCount(TASK_UNITS),
+
+ /**
+ * The number of non-follow-up tasks that have been completed successfully.
+ */
+ taskSuccessCount(TASK_UNITS),
+
+ /**
+ * The number of non-follow-up tasks that have been completed with a failure.
+ */
+ taskFailureCount(TASK_UNITS),
+
+ /**
+ * The number of non-follow-up tasks that have been aborted.
+ */
+ taskAbortCount(TASK_UNITS),
+
+ /**
+ * The number of follow-up tasks that have made the round trip from being
+ * scheduled to the point where they are completely handled.
+ */
+ followUpCompleteCount(TASK_UNITS),
+
+ /**
+ * The number of follow-up tasks that have been completed successfully.
+ */
+ followUpSuccessCount(TASK_UNITS),
+
+ /**
+ * The number of follow-up tasks that have been completed with a failure.
+ */
+ followUpFailureCount(TASK_UNITS),
+
+ /**
+ * The average number of milliseconds for a task to be handled by the
+ * {@link TaskHandler} via
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)}.
+ */
+ averageHandleTask(MILLISECOND_UNITS),
+
+ /**
+ * The number of times the
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)} method has been
+ * called to handle a task (follow-up or not).
+ */
+ handleTaskCount(CALL_UNITS),
+
+ /**
+ * The number of times that the
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)} has been called
+ * successfully (i.e.: without any exceptions) to handle a task (follow-up or
+ * not).
+ */
+ handleTaskSuccessCount(CALL_UNITS),
+
+ /**
+ * The number of times that the
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)} has been called
+ * unsuccessfully (i.e.: with an exceptions being thrown) to handle a task
+ * (follow-up or not).
+ */
+ handleTaskFailureCount(CALL_UNITS),
+
+ /**
+ * Gets the ratio of the number of times
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)} has been called for
+ * follow-up tasks to number of times it has been called for all tasks
+ * that have been handled.
+ */
+ followUpHandleTaskRatio(null),
+
+ /**
+ * The number of non-followup tasks that have made the round trip from being
+ * scheduled to the point where they are completely handled. Some messages may
+ * make the round trip more than once if a failure occurs in processing part or
+ * all of the message.
+ */
+ taskGroupCompleteCount(TASK_GROUP_UNITS),
+
+ /**
+ * The number of task groups that had all of their tasks handled successfully
+ * without any exceptions.
+ */
+ taskGroupSuccessCount(TASK_GROUP_UNITS),
+
+ /**
+ * The number of task groups that have completed but have had at least one
+ * failure with one of the associated tasks.
+ */
+ taskGroupFailureCount(TASK_GROUP_UNITS),
+
+ /**
+ * The average compression ratio of duplicate non-follow-up tasks. This is the
+ * number of total non-follow-up tasks handled divided by the number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} was called to
+ * handle those tasks.
+ */
+ averageCompression(TASKS_PER_CALL_UNITS),
+
+ /**
+ * The greatest compression ratio achieved by a single non-follow-up task. This
+ * the greatest number of duplicate non-follow-up tasks that were collapsed into
+ * a single task handling call to
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)}.
+ */
+ greatestCompression(TASKS_PER_CALL_UNITS),
+
+ /**
+ * The average compression ratio of duplicate follow-up tasks. This is the
+ * number of total follow-up tasks handled divided by the number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} was called to
+ * handle those tasks.
+ */
+ averageFollowUpCompression(TASKS_PER_CALL_UNITS),
+
+ /**
+ * The greatest compression ratio achieved by a single follow-up task. This the
+ * greatest number of duplicate follow-up tasks that were collapsed into a
+ * single task handling call to
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)}.
+ */
+ greatestFollowUpCompression(TASKS_PER_CALL_UNITS),
+
+ /**
+ * The average number of tasks in each task group. This only considers
+ * non-follow-up tasks.
+ */
+ averageTaskGroupSize(TASK_UNITS),
+
+ /**
+ * The greatest number of tasks encountered in a completed task group. This only
+ * considers non-follow-up tasks.
+ */
+ greatestTaskGroupSize(TASK_UNITS),
+
+ /**
+ * The ratio of cumulative {@link TaskHandler} handling time across all threads
+ * to actual active handling time.
+ */
+ parallelism(null),
+
+ /**
+ * The ratio of the number of times the {@link #dequeueTask()} function is
+ * called and a task is ready to be returned without waiting.
+ */
+ dequeueHitRatio(null),
+
+ /**
+ * The greatest number of tasks to be postponed at any given time due to
+ * contention on the resources being acted upon.
+ */
+ greatestPostponedCount(TASK_UNITS),
+
+ /**
+ * The cumulative time spent (in milliseconds) in the {@link #handleTasks()}
+ * function.
+ */
+ taskHandling(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative time spent (in milliseconds) actively handling tasks. This
+ * excludes time waiting for messages to arrive.
+ */
+ activelyHandling(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative time spent (in milliseconds) waiting for tasks to be
+ * scheduled.
+ */
+ waitingForTasks(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative time spent (in milliseconds) not handling tasks while waiting
+ * for locks to be released for postponed tasks.
+ */
+ waitingOnPostponed(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) between handing a task off to a worker for
+ * processing and obtaining the next task to be processed.
+ */
+ betweenTasks(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) calling {@link #dequeueTask()} function to
+ * dequeue a task from the internal queue. This includes time waiting for the
+ * first task to arrive or the next task to arrive after the previous task has
+ * been handled.
+ */
+ dequeue(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) waiting to obtain the synchronized lock on
+ * the scheduling service in order to call the {@link #dequeueTask()} function.
+ */
+ dequeueBlocking(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) in the "wait loop" of {@link #dequeueTask()}
+ * waiting for a task to become available for processing.
+ */
+ dequeueTaskWaitLoop(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) in the synchronization wait of
+ * {@link #dequeueTask()} waiting for a task to become available for processing.
+ * This should be the majority of the time spent in
+ * {@link #dequeueTaskWaitLoop}, but isolates the non-busy sleeping time
+ * awaiting notification of task arrival.
+ */
+ dequeueTaskWait(MILLISECOND_UNITS),
+
+ /**
+ * The time spent (in milliseconds) checking to see if a task on the pending
+ * queue is locked and should be postponed for later processing.
+ */
+ dequeueCheckLocked(MILLISECOND_UNITS),
+
+ /**
+ * The number of milliseconds spent calling
+ * {@link #init(JsonObject, TaskHandler)}.
+ */
+ initialize(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent checking pending tasks to see if
+ * one is ready to be processed.
+ */
+ checkPending(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent checking follow-up tasks to see
+ * if they are now ready to be processed.
+ */
+ checkFollowUp(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent checking postponed tasks to see
+ * if they are now ready to be processed.
+ */
+ checkPostponed(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent obtaining locks on the affected
+ * resources.
+ */
+ obtainLocks(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent waiting for an available worker
+ * thread to handle a task that has been pulled from the pending queue.
+ */
+ waitForWorker(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent calling
+ * {@link TaskHandler#handleTask(String,Map,int,Scheduler)}.
+ */
+ handleTask(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent calling
+ * {@link ScheduledTask#succeeded()} or {@link ScheduledTask#failed(Exception)}.
+ */
+ markComplete(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent calling
+ * {@link #completeFollowUpTask(ScheduledTask)}.
+ */
+ completeFollowUp(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent releasing locks on affected
+ * resources.
+ */
+ releaseLocks(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent calling
+ * {@link #postProcess(ScheduledTask)}.
+ */
+ postProcess(MILLISECOND_UNITS),
+
+ /**
+ * The cumulative number of milliseconds spent calling {@link #destroy()}.
+ */
+ destroy("ms");
+
+ /**
+ * Constructs the statistic instance with the associated units.
+ *
+ * @param units The units that the statistic is measured in.
+ */
+ Stat(String units) {
+ this.units = units;
+ }
+
+ /**
+ * The units for the statistic.
+ */
+ private String units;
+
+ /**
+ * Gets the unit of measure for this statistic. This is the unit that the
+ * {@link Number} value is measured in when calling
+ * {@link AbstractSchedulingService#getStatistics()}}
+ *
+ * @return The unit of measure for this statistic.
+ */
+ public String getUnits() {
+ return this.units;
+ }
+ }
/**
- * The number of task groups that had all of their tasks handled
- * successfully without any exceptions.
+ * The {@link TaskHandler} for handling the tasks.
*/
- taskGroupSuccessCount(TASK_GROUP_UNITS),
+ private TaskHandler taskHandler = null;
/**
- * The number of task groups that have completed but have had at least one
- * failure with one of the associated tasks.
+ * The {@link LockingService} to use.
*/
- taskGroupFailureCount(TASK_GROUP_UNITS),
+ private LockingService lockingService = null;
/**
- * The average compression ratio of duplicate non-follow-up tasks. This is
- * the number of total non-follow-up tasks handled divided by the number of
- * times {@link TaskHandler#handleTask(String, Map, int, Scheduler)} was called
- * to handle those tasks.
+ * The {@link State} for this instance.
*/
- averageCompression(TASKS_PER_CALL_UNITS),
+ private State state = UNINITIALIZED;
/**
- * The greatest compression ratio achieved by a single non-follow-up task.
- * This the greatest number of duplicate non-follow-up tasks that were
- * collapsed into a single task handling call to {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)}.
+ * The {@link List} of {@link TaskType} instances specifying the
+ * {@link TaskType} order.
*/
- greatestCompression(TASKS_PER_CALL_UNITS),
+ private Listtrue if this instance is still handling tasks,
- * otherwise false.
- *
- */
- protected synchronized boolean isHandlingTasks() {
- return this.handlingTasks;
- }
-
- /**
- * The {@link Object} to synchronize on when computing and recording
- * statistics in a thread-safe manner.
- *
- * @return The {@link Object} to synchronize on when computing and recording
- * statistics in a thread-safe manner.
- */
- protected final Object getStatisticsMonitor() {
- return this.statsMonitor;
- }
-
- /**
- * Gets the concurrency of the scheduler -- this is the number of threads it
- * will use to handle tasks. The returned value will be a positive number
- * greater than or equal to one (1).
- *
- * @return The concurrency of the scheduler (i.e.: the number of threads it
- * will use to handle tasks).
- */
- public int getConcurrency() {
- return this.concurrency;
- }
-
- /**
- * Gets the default concurrency with which to initialize if one is not specified
- * in the initialization configuration via the {@link #CONCURRENCY_KEY}
- * initialization parameter. By default, this returns {@link
- * #DEFAULT_CONCURRENCY}, but it may be overridden to return something more
- * sensible for a derived implementation.
- *
- * @return The default concurrency with which to initialize.
- *
- * @see #getConcurrency()
- * @see #CONCURRENCY_KEY
- * @see #DEFAULT_CONCURRENCY
- */
- public int getDefaultConcurrency() {
- return DEFAULT_CONCURRENCY;
- }
-
- /**
- * Gets the number of milliseconds to sleep between checks on the locks
- * required for tasks that have been postponed due to contention. This
- * timeout is used when there are pending tasks that have been postponed
- * due to contention.
- *
- * @return The number of milliseconds to sleep between checks on the locks
- * required for tasks that have been postponed due to contention.
- */
- public long getPostponedTimeout() {
- return this.postponedTimeout;
- }
-
- /**
- * Gets the default postponed timeout with which to initialize if one is not
- * specified in the initialization configuration via the {@link
- * #POSTPONED_TIMEOUT_KEY} initialization parameter. By default, this returns
- * {@link #DEFAULT_POSTPONED_TIMEOUT}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default postponed timeout with which to initialize.
- *
- * @see #getPostponedTimeout()
- * @see #POSTPONED_TIMEOUT_KEY
- * @see #DEFAULT_POSTPONED_TIMEOUT
- */
- public long getDefaultPostponedTimeout() {
- return DEFAULT_POSTPONED_TIMEOUT;
- }
-
- /**
- * Gets the number of milliseconds to delay before attempting to execute a
- * follow-up task. This delay is used to give the opportunity to receive
- * duplicate follow-up tasks that can be collapsed. Whenever a duplicate is
- * collapsed, the delay timer starts over unless the {@linkplain
- * #getFollowUpTimeout() maximum follow-up deferral time} has been reached.
- *
- * @return The number of milliseconds to delay before attempting to execute a
- * follow-up task.
- */
- public long getFollowUpDelay() {
- return this.followUpDelay;
- }
-
- /**
- * Gets the default follow-up delay with which to initialize if one is not
- * specified in the initialization configuration via the {@link
- * #FOLLOW_UP_DELAY_KEY} initialization parameter. By default, this returns
- * {@link #DEFAULT_FOLLOW_UP_DELAY}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default follow-up delay with which to initialize.
- *
- * @see #getFollowUpDelay()
- * @see #FOLLOW_UP_DELAY_KEY
- * @see #DEFAULT_FOLLOW_UP_DELAY
- */
- public long getDefaultFollowUpDelay() {
- return DEFAULT_FOLLOW_UP_DELAY;
- }
-
- /**
- * The maximum number of milliseconds to defer a follow-up task. Once a
- * follow-up task has been deferred this number of milliseconds it will no
- * longer be purposely delayed to wait for additional duplicates to be
- * scheduled and collapsed. It may be delayed because of a lack of resources
- * to handle it.
- *
- * @return The maximum number of milliseconds to defer a follow-up task.
- */
- public long getFollowUpTimeout() {
- return this.followUpTimeout;
- }
-
- /**
- * Gets the default follow-up timeout with which to initialize if one is not
- * specified in the initialization configuration via the {@link
- * #FOLLOW_UP_TIMEOUT_KEY} initialization parameter. By default, this returns
- * {@link #DEFAULT_FOLLOW_UP_TIMEOUT}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default follow-up timeout with which to initialize.
- *
- * @see #getFollowUpTimeout()
- * @see #FOLLOW_UP_TIMEOUT_KEY
- * @see #DEFAULT_FOLLOW_UP_TIMEOUT
- */
- public long getDefaultFollowUpTimeout() {
- return DEFAULT_FOLLOW_UP_TIMEOUT;
- }
-
- /**
- * Gets the number of milliseconds to lease follow-up messages for handling
- * before they become available to be obtained again. The default
- * implementation returns twice the {@linkplain #getFollowUpTimeout()
- * follow-up timeout}.
- *
- * @return The number of milliseconds to lease follow-up messages for handling
- * before they become available to be obtained again.
- */
- public long getFollowUpLeaseTime() {
- return this.getFollowUpTimeout() * 2;
- }
-
- /**
- * The configured maximum number of follow-up tasks to retrieve from
- * persistent search on a single retrieval. The retrieved follow-up tasks
- * should be handled within the {@linkplain #getFollowUpTimeout() follow-up
- * timeout} and so this number should not be so large that the tasks are not
- * handled or their retrieval is renewed within the allotted time.
- *
- * @return The configured maximum number of follow-up tasks to retrieve from
- * persistent storage on a single retrieval.
- */
- public int getFollowUpFetchCount() {
- return this.followUpFetch;
- }
-
- /**
- * Gets the default follow-up fetch count with which to initialize if one is
- * not specified in the initialization configuration via the {@link
- * #FOLLOW_UP_FETCH_KEY} initialization parameter. By default, this returns
- * {@link #DEFAULT_FOLLOW_UP_FETCH}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default follow-up fetch count with which to initialize.
- *
- * @see #getFollowUpFetchCount()
- * @see #FOLLOW_UP_FETCH_KEY
- * @see #DEFAULT_FOLLOW_UP_FETCH
- */
- public int getDefaultFollowUpFetchCount() {
- return DEFAULT_FOLLOW_UP_FETCH;
- }
-
- /**
- * Gets the number of milliseconds to sleep between checking to see if task
- * handling should cease. This timeout is used when there are no postponed
- * tasks due to contention.
- *
- * @return The number of milliseconds to sleep between checking to see if
- * task handling should cease. This timeout is used when there
- * are no postponed tasks due to contention.
- */
- public long getStandardTimeout() {
- return this.standardTimeout;
- }
-
- /**
- * Gets the default standard timeout with which to initialize if one is not
- * specified in the initialization configuration via the {@link
- * #STANDARD_TIMEOUT_KEY} initialization parameter. By default, this returns
- * {@link #DEFAULT_STANDARD_TIMEOUT}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default standard timeout with which to initialize.
- *
- * @see #getStandardTimeout()
- * @see #STANDARD_TIMEOUT_KEY
- * @see #DEFAULT_STANDARD_TIMEOUT
- */
- public long getDefaultStandardTimeout() {
- return DEFAULT_STANDARD_TIMEOUT;
- }
-
- /**
- * Gets the {@link TaskHandler} for this instance.
- *
- * @return The {@link TaskHandler} for this instance.
- */
- @Override
- public TaskHandler getTaskHandler() {
- return this.taskHandler;
- }
-
- /**
- * Sets the {@link TaskHandler} for this instance.
- *
- * @param taskHandler The {@link TaskHandler} for this instance.
- */
- protected void setTaskHandler(TaskHandler taskHandler) {
- this.taskHandler = taskHandler;
- }
-
- /**
- * Gets the {@link LockingService} for this instance.
- *
- * @return The {@link LockingService} for this instance.
- */
- @Override
- public LockingService getLockingService() {
- return this.lockingService;
- }
-
- /**
- * Sets the {@link LockingService} for this instance.
- *
- * @param lockingService The {@link LockingService} for this instance.
- */
- protected void setLockingService(LockingService lockingService) {
- this.lockingService = lockingService;
- }
-
- /**
- * Gets the default {@link LockingService} class name with which to
- * initialize the backing {@link LockingService} if one is not specified in
- * the initialization configuration via the {@link #LOCKING_SERVICE_CLASS_KEY}
- * initialization parameter. By default, this returns the {@link
- * #DEFAULT_LOCKING_SERVICE_CLASS_NAME}, but it may be overridden to return
- * something more sensible for a derived implementation.
- *
- * @return The default {@link LockingService} class name with which to
- * initialize.
- *
- * @see #initLockingService(JsonObject)
- * @see #getDefaultLockingServiceConfig()
- * @see #LOCKING_SERVICE_CLASS_KEY
- * @see #LOCKING_SERVICE_CONFIG_KEY
- * @see #DEFAULT_LOCKING_SERVICE_CLASS_NAME
- */
- public String getDefaultLockingServiceClassName() {
- return DEFAULT_LOCKING_SERVICE_CLASS_NAME;
- }
-
- /**
- * Gets the default {@link JsonObject} configuration with which to initialize
- * the backing {@link LockingService} if one is not specified in the
- * initialization configuration via the {@link #LOCKING_SERVICE_CONFIG_KEY}
- * initialization parameter. By default, this returns the null,
- * but it may be overridden to return something more sensible for a derived
- * implementation.
- *
- * @return The default {@link JsonObject} configuration with which to
- * initialize the backing {@link LockingService}.
- *
- * @see #initLockingService(JsonObject)
- * @see #getDefaultLockingServiceClassName()
- * @see #LOCKING_SERVICE_CLASS_KEY
- * @see #LOCKING_SERVICE_CONFIG_KEY
- * @see #DEFAULT_LOCKING_SERVICE_CLASS_NAME
- */
- public JsonObject getDefaultLockingServiceConfig() {
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Scheduler createScheduler(boolean followUp) {
- if (followUp) {
- return new DefaultScheduler(this);
- } else {
- TaskGroup taskGroup = new TaskGroup();
- return new DefaultScheduler(this, taskGroup);
- }
- }
-
- /**
- * Creates a {@link Scheduler} for creating follow-up tasks to the specified
- * {@link ScheduledTask} unless follow-up tasks are not supported for the
- * specified {@link ScheduledTask}. The default implementation will
- * always create a {@link DefaultScheduler} that will not have an
- * associated {@link TaskGroup}.
- *
- * @param task The {@link ScheduledTask} for which to create the follow-up
- * scheduler.
- * @return The follow-up {@link Scheduler} or null if follow-up
- * tasks are not allowed for the specified {@link ScheduledTask}.
- */
- protected Scheduler createFollowUpScheduler(ScheduledTask task) {
- // create a follow-up scheduler
- return new DefaultScheduler(this);
- }
-
- /**
- * Schedules the tasks in the specified {@link List}.
- *
- * @param tasks The {@link List} of {@link Task} instances.
- *
- * @throws ServiceExecutionException If a failure occurs in scheduling the
- * tasks. If a failure occurs then it
- * should be assumed that the tasks will
- * not be handled and the associated
- * message should be retried later.
- */
- protected void scheduleTasks(Listnull is returned.
- *
- * @return The next pending {@link ScheduledTask} that is now ready to try, or
- * null if none are ready to try.
- */
- protected synchronized ScheduledTask getReadyPendingTask() {
- this.timerStart(dequeueCheckLocked);
- try {
- // if none ready then check if we can grab a pending task
- while (this.pendingTasks.size() > 0) {
- // get the candidate task
- ScheduledTask task = this.pendingTasks.remove(0);
-
- // check if the task is aborted
- if (this.skipIfAborted(task)) {
- continue;
- }
-
- // attempt to lock the task resources
- this.timerStart(obtainLocks);
- boolean locked = task.acquireLocks(this.getLockingService());
- this.timerPause(obtainLocks);
-
- // if the lock was obtained, return the task
- if (locked) {
- return task;
- }
-
- // if not locked then postpone the task
- this.postponedTasks.add(task);
-
- // check the postponed count to see if this is now the greatest
- synchronized (this.getStatisticsMonitor()) {
- int postponedCount = this.postponedTasks.size();
- if (postponedCount > this.greatestPostponedCount) {
- this.greatestPostponedCount = postponedCount;
- }
- }
- // notify all
- this.notifyAll();
- }
-
- // if we get here then return null
- return null;
-
- } finally {
- this.timerPause(dequeueCheckLocked);
- }
- }
-
- /**
- * Returns a previously postponed {@link ScheduledTask} that is now ready to
- * be processed. If the last time this method was called was less than the
- * {@linkplain #getPostponedTimeout() postpone timeout} then this method returns
- * null so that the previously postponed tasks are not checked
- * for readiness too frequently. Otherwise, this method will find the least
- * recently postponed {@link ScheduledTask} whose set of affected resources
- * (identified by {@link ResourceKey} instances) are not currently locked.
- * If there are no postponed {@link ScheduledTask} instance that meet the
- * readiness criteria, then null is returned.
- *
- * @return The next postponed {@link ScheduledTask} that is now ready to try.
- */
- protected synchronized ScheduledTask getReadyPostponedTask() {
- // get the elapsed time and update the timestamp
- long now = System.nanoTime();
- long elapsedNanos = now - this.postponedNanoTime;
- long elapsedMillis = elapsedNanos / ONE_MILLION;
-
- // check the timestamp
- if (elapsedMillis < this.getPostponedTimeout()) {
- return null;
- }
-
- // check if there are no postponed messages
- if (this.postponedTasks.size() == 0) {
- // since we have checked all the postponed messages (none) and none are
- // ready then we need to update the timestamp
- this.postponedNanoTime = now;
-
- return null;
- }
-
- // iterate through the postponed messages
- Iteratortrue if the specified
- * {@link ScheduledTask} can be fully removed from the queue and ignored
- * (i.e.: it has no more backing tasks). If not all backing tasks are
- * aborted, then false is returned to indicate the task still
- * needs to be handled.
- *
- * @param task The {@link ScheduledTask} to check if fully aborted and remove
- * aborted tasks from.
- * @return true if the specified {@link ScheduledTask} should be
- * skipped because it is fully aborted, otherwise false.
- */
- protected boolean skipIfAborted(ScheduledTask task) {
- // remove any aborted tasks
- int abortCount = task.removeAborted();
- this.taskAbortCount += abortCount;
-
- // check if aborted
- if (task.getMultiplicity() == 0) {
- if (task.isAllowingCollapse()) {
- ScheduledTask collapse = this.taskCollapseLookup.get(task.getSignature());
- if (collapse == task) {
- this.taskCollapseLookup.remove(task.getSignature());
- } else {
- throw new IllegalStateException(
- "Unexpected collapsing task in lookup. expected=[ "
- + task + " ], found=[ " + collapse + " ]");
- }
- }
- return true;
- }
-
- // return false if we get here
- return false;
- }
-
- /**
- * Checks if a check should be performed against the readiness of the
- * postponed tasks. This returns true if and only if there is
- * at least one postponed task and the readiness check has not been
- * performed within the configured postponed timeout.
- *
- * @return true if it is time to perform a postponed task
- * readiness check, otherwise false.
- */
- protected synchronized boolean isPostponedReadyCheckTime() {
- // no need to do a ready check if no postponed messages
- if (this.postponedTasks.size() == 0) {
- return false;
- }
-
- // get the elapsed time and update the timestamp
- long now = System.nanoTime();
- long elapsedNanos = now - this.postponedNanoTime;
- long elapsedMillis = elapsedNanos / ONE_MILLION;
-
- // check the timestamp
- return (elapsedMillis >= this.getPostponedTimeout());
- }
-
- /**
- * Returns a previously scheduled follow-up {@link ScheduledTask} that is now
- * ready to be processed. If the resources that must be locked are not
- * available for the follow-up task then it is left on the queue. If the
- * last time this method was called was less than the
- * {@linkplain #getPostponedTimeout() postpone timeout} then this method returns
- * null so that the previously postponed tasks are not checked
- * for readiness too frequently. Otherwise, this method will find the least
- * recently postponed {@link ScheduledTask} whose set of affected resources
- * (identified by {@link ResourceKey} instances) are not currently locked.
- * If there are no postponed {@link ScheduledTask} instance that meet the
- * readiness criteria, then null is returned.
- *
- * @return The next postponed {@link ScheduledTask} that is now ready to try.
- * @throws ServiceExecutionException If a failure occurs in obtaining a
- * follow-up task.
- */
- protected synchronized ScheduledTask getReadyFollowUpTask()
- throws ServiceExecutionException {
- // get the current timestamp
- long now = System.nanoTime();
-
- // check if there are no follow-up messages
- if (this.followUpTasks.size() <= 1) {
- // we have no follow-up tasks in the cache, let's get some
- Listtrue if and only if there is
- * at least one follow-up task and the readiness check has not been
- * performed within the configured follow-up timeout.
- *
- * @return true if it is time to perform a postponed task
- * readiness check, otherwise false.
- */
- protected synchronized boolean isFollowUpReadyCheckTime() {
- // get the elapsed time and update the timestamp
- long now = System.nanoTime();
- long elapsedNanos = now - this.followUpNanoTime;
- long elapsedMillis = elapsedNanos / ONE_MILLION;
-
- // check the timestamp
- return (elapsedMillis >= (this.getFollowUpDelay() / 2));
- }
-
- /**
- * Enqueues the specified follow-up {@link Task} instance and persists it for
- * future retrieval. A follow-up {@link Task} does not belong to a
- * {@link TaskGroup} and therefore should have a null
- * {@linkplain Task#getTaskGroup() task group property}.
- *
- * @param task The follow-up {@link Task} to enqueue.
- *
- * @throws IllegalArgumentException If any of the specified {@link Task}
- * belongs
- * to a {@link TaskGroup}.
- *
- * @throws ServiceExecutionException If a failure occurs in persisting the
- * specified {@link Task} instances
- *
- */
- protected abstract void enqueueFollowUpTask(Task task)
- throws ServiceExecutionException;
-
- /**
- * Retrieves a number of follow-up tasks from persistent storage.
- * This should mark the retrieved tasks as pending and should not return
- * them again until at least after {@link #getFollowUpTimeout()}
- * milliseconds has past.
- *
- * @param count The suggested number of follow-up tasks to retrieve from
- * persistent storage.
- *
- * @return The {@link List} of follow-up {@link Task} instances retrieved
- * from persistent storage.
- *
- * @throws ServiceExecutionException If a failure occurs in persisting the
- * specified {@link Task} instances
- */
- protected abstract Listnull if
- * no result was returned.
- */
- protected void handleAsyncResult(AsyncResulttrue if the worker pool is busy, otherwise
- * false.
- */
- protected void toggleActiveAndWaitingTimers(int pendingCount,
- int postponedCount,
- boolean busy) {
- synchronized (this.getStatisticsMonitor()) {
- // check if there are messages
- if (busy) {
- this.timerPause(waitingForTasks, waitingOnPostponed);
- this.timerStart(activelyHandling);
-
- } else if (pendingCount == 0 && postponedCount == 0) {
- // no tasks pending or postponed
- this.timerPause(activelyHandling, waitingOnPostponed);
- this.timerStart(waitingForTasks);
-
- } else if (pendingCount > 0) {
- // messages pending
- this.timerPause(waitingForTasks, waitingOnPostponed);
- this.timerStart(activelyHandling);
-
- } else if (postponedCount > 0) {
- // none pending, but some postponed
- this.timerPause(activelyHandling, waitingForTasks);
- this.timerStart(waitingOnPostponed);
- }
- }
- }
-
- /**
- * Resumes the associated {@link Timers} in a thread-safe manner.
- *
- * @param statistic The {@link Stat} to resume.
- * @param addlTimers The additional {@link Stat} instances to
- * resume.
- */
- protected void timerResume(Stat statistic,
- Stat... addlTimers) {
- String[] names = this.convertTimerKeys(addlTimers);
- synchronized (this.getStatisticsMonitor()) {
- if (names == null) {
- this.timers.resume(statistic.toString());
- } else {
- this.timers.resume(statistic.toString(), names);
- }
- }
- }
-
- /**
- * Starts the associated {@link Timers} in a thread-safe manner.
- *
- * @param statistic The {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat} to start.
- * @param addlTimers The additional {@link Stat} instances to
- * start.
- */
- protected void timerStart(Stat statistic,
- Stat... addlTimers) {
- String[] names = this.convertTimerKeys(addlTimers);
- synchronized (this.getStatisticsMonitor()) {
- if (names == null) {
- this.timers.start(statistic.toString());
- } else {
- this.timers.start(statistic.toString(), names);
- }
- }
- }
-
- /**
- * Pauses the associated {@link Timers} in a thread-safe manner.
- *
- * @param statistic The {@link Stat} to pause.
- * @param addlTimers The additional {@link Stat} instances to
- * pause.
- */
- protected void timerPause(Stat statistic,
- Stat... addlTimers) {
- String[] names = this.convertTimerKeys(addlTimers);
- synchronized (this.getStatisticsMonitor()) {
- if (names == null) {
- this.timers.pause(statistic.toString());
- } else {
- this.timers.pause(statistic.toString(), names);
- }
- }
- }
-
- /**
- * Gets the {@link Map} of {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat} keys to
- * their {@link Number} values in an atomic thread-safe manner.
- *
- * @return The {@link Map} of {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat} keys
- * to their {@link Number} values.
- */
- @Override
- public Mapnull if no
- * non-follow-up tasks have been handled.
- *
- * @return The average task compression from collapsing non-follow-up tasks
- * handled by the scheduling service, or null if no
- * non-follow-up tasks have been handled.
- */
- public Double getAverageCompressionRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.standardHandleCount == 0) {
- return null;
- }
- double completeCount = (double) this.taskCompleteCount;
- double handleCount = (double) this.standardHandleCount;
- return completeCount / handleCount;
- }
- }
-
- /**
- * Gets the greatest task compression from collapsing non-follow-up tasks
- * handled by the scheduling service. This returns null if
- * no tasks have been handled.
- *
- * @return The greatest task compression from collapsing non-follow-up tasks
- * handled by the scheduling service, or null if no
- * tasks have been handled.
- */
- public Integer getGreatestCompressionRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.greatestMultiplicity <= 0) {
- return null;
- }
- return this.greatestMultiplicity;
- }
- }
-
- /**
- * Gets the average task compression from collapsing follow-up tasks
- * handled by the scheduling service. This returns null if no
- * follow-up tasks have been handled.
- *
- * @return The average task compression from collapsing follow-up tasks
- * handled by the scheduling service, or null if no
- * follow-up tasks have been handled.
- */
- public Double getAverageFollowUpCompressionRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.followUpHandleCount == 0) {
- return null;
- }
- double completeCount = (double) this.followUpCompleteCount;
- double handleCount = (double) this.followUpHandleCount;
- return completeCount / handleCount;
- }
- }
-
- /**
- * Gets the greatest task compression from collapsing follow-up tasks
- * handled by the scheduling service. This returns null if
- * no follow-up tasks have been handled.
- *
- * @return The greatest task compression from collapsing follow-up tasks
- * handled by the scheduling service, or null if no
- * follow-up tasks have been handled.
- */
- public Integer getGreatestFollowUpCompressionRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.greatestFollowUpMultiplicity <= 0) {
- return null;
- }
- return this.greatestFollowUpMultiplicity;
- }
- }
-
- /**
- * Gets the average number of tasks in all the completed task groups. This
- * returns null if no task groups have been completed.
- *
- * @return The average number of tasks in all the completed task groups, or
- * null if no task groups have been completed.
- */
- public Double getAverageTaskGroupSize() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.taskGroupCount == 0) {
- return null;
- }
- double completeCount = (double) this.taskCompleteCount;
- double groupCount = (double) this.taskGroupCount;
- return (completeCount / groupCount);
- }
- }
-
- /**
- * Gets the dequeue hit ratio. This returns null if there have
- * been no attempts to dequeue a task.
- *
- * @return The dequeue hit ratio, or null if no attempts have
- * been made to dequeue a task.
- */
- public Double getDequeueHitRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if ((this.dequeueHitCount + this.dequeueMissCount) == 0) {
- return null;
- }
- double hits = (double) this.dequeueHitCount;
- double misses = (double) this.dequeueMissCount;
- double total = hits + misses;
- return (hits / total);
- }
- }
-
- /**
- * Call this to increment the number of times dequeue has been called with
- * or without a task ready to be dequeued. This function is thread-safe
- * with respect to other statistics.
- *
- * @param hit true if we have a "hit" and there is a task ready
- * to be dequeued, otherwise false for a "miss".
- */
- protected void updateDequeueHitRatio(boolean hit) {
- synchronized (this.getStatisticsMonitor()) {
- if (hit) {
- this.dequeueHitCount++;
- } else {
- this.dequeueMissCount++;
- }
- }
- }
-
- /**
- * The average time in milliseconds that non-follow-up tasks have taken from
- * scheduling until completion. This returns null if no
- * non-follow-up tasks have been handled.
- *
- * @return The average time in milliseconds that non-follow-up tasks have
- * taken from scheduling until completion, or null if
- * no non-follow-up tasks have been handled.
- */
- public Double getAverageTaskTime() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.taskCompleteCount == 0) {
- return null;
- }
- double totalTime = (double) this.totalTaskTime;
- double completeCount = (double) this.taskCompleteCount;
- return totalTime / completeCount;
- }
- }
-
- /**
- * The longest time in milliseconds that a non-follow-up task has taken from
- * scheduling until completion. This returns null if no
- * non-follow-up tasks have been handled.
- *
- * @return The longest time in milliseconds that a non-follow-up task has
- * taken from scheduling until completion, or null if
- * no non-follow-up tasks have been handled.
- */
- public Long getLongestTaskTime() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.longestTaskTime < 0) {
- return null;
- }
- return this.longestTaskTime;
- }
- }
-
- /**
- * Gets the average number of milliseconds from all task groups to be
- * handled from the time first task in the group was scheduled until the last
- * task was completed. This returns null if no task groups
- * have been completed.
- *
- * @return The average number of milliseconds from all task groups to be
- * handled from the time first task in the group was scheduled until
- * the last task was completed, or null if no task
- * groups have been completed.
- */
- public Double getAverageTaskGroupTime() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.taskGroupCount == 0) {
- return null;
- }
- double totalTime = (double) this.totalTaskGroupTime;
- double groupCount = (double) this.taskGroupCount;
- return totalTime / groupCount;
- }
- }
-
- /**
- * Gets the greatest number of milliseconds for a task groups to be handled
- * from the time first task in the group was scheduled until the last task
- * was completed. This returns null if no task groups have been
- * completed.
- *
- * @return The greatest number of milliseconds for a task groups to be handled
- * from the time first task in the group was scheduled until the last
- * task was completed, or null if no task groups have
- * been completed.
- */
- public Long getLongestTaskGroupTime() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.longestTaskGroupTime < 0L) {
- return null;
- }
- return this.longestTaskGroupTime;
- }
- }
-
- /**
- * Gets the number of non-follow-up tasks that have been completed.
- *
- * @return The number of non-follow-up tasks that have been completed.
- */
- public long getCompletedTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.taskCompleteCount;
- }
- }
-
- /**
- * Gets the number of non-follow-up tasks that have been completed
- * successfully.
- *
- * @return The number of non-follow-up tasks that have been completed
- * successfully.
- */
- public long getSuccessfulTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.taskSuccessCount;
- }
- }
-
- /**
- * Gets the number of non-follow-up tasks that have been completed
- * unsuccessfully (i.e.: with failures).
- *
- * @return The number of non-follow-up tasks that have been completed
- * unsuccessfully (i.e.: with failures).
- */
- public long getFailedTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.taskFailureCount;
- }
- }
-
- /**
- * Gets the number of non-follow-up tasks that were aborted.
- *
- * @return The number of non-follow-up tasks that were aborted.
- */
- public long getAbortedTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.taskAbortCount;
- }
- }
-
- /**
- * Gets the number of follow-up tasks that have been completed.
- *
- * @return The number of follow-up tasks that have been completed.
- */
- public long getCompletedFollowUpCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.followUpCompleteCount;
- }
- }
-
- /**
- * Gets the number of follow-up tasks that have been completed successfully.
- *
- * @return The number of follow-up tasks that have been completed
- * successfully.
- */
- public long getSuccessfulFollowUpCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.followUpSuccessCount;
- }
- }
-
- /**
- * Gets the number of follow-up tasks that have been completed unsuccessfully
- * (i.e.: with failures).
- *
- * @return The number of follow-up tasks that have been completed
- * successfully (i.e.: with failures).
- */
- public long getFailedFollowUpCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.followUpFailureCount;
- }
- }
-
- /**
- * Get the average number of milliseconds spent calling {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} for tasks (both
- * follow-up
- * and non-follow-up). If no tasks have been handled then null
- * is returned.
- *
- * @return The average number of milliseconds spent calling {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} for tasks, or
- * null if no tasks have been handled.
- */
- public Double getAverageHandleTaskTime() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.handleCount == 0) {
- return null;
- }
- double totalTime = ((double) this.totalHandlingTime);
- double callCount = ((double) this.handleCount);
- return totalTime / callCount;
- }
- }
-
- /**
- * Get the total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called to
- * handle tasks (both follow-up and non-follow-up).
- *
- * @return The total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
- * to handle tasks (both follow-up and non-follow-up).
- */
- public long getHandleTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.handleCount;
- }
- }
-
- /**
- * Get the total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called to
- * handle tasks successfully (both follow-up and non-follow-up).
- *
- * @return The total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
- * to handle tasks successfully (both follow-up and non-follow-up).
- */
- public long getSuccessfulHandleTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.handleSuccessCount;
- }
- }
-
- /**
- * Get the total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called to
- * handle tasks unsuccessfully (both follow-up and non-follow-up).
- *
- * @return The total number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
- * to handle tasks unsuccessfully (both follow-up and non-follow-up).
- */
- public long getFailedHandleTaskCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.handleFailureCount;
- }
- }
-
- /**
- * Gets the ratio of the number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called to
- * handle
- * follow-up tasks to the number of times it has been called to handle
- * all tasks that have been handled. This returns null
- * if no tasks have been handled.
- *
- * @return The ratio of the number of times {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
- * to handle follow-up tasks to the number of times it has been
- * called to handle all tasks that have been handled, or
- * null if no tasks have been handled.
- */
- public Double getFollowUpHandleTaskRatio() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.handleCount == 0) {
- return null;
- }
- double followUp = ((double) this.followUpHandleCount);
- double all = ((double) this.handleCount);
- return followUp / all;
- }
- }
-
- /**
- * Gets the number of {@link TaskGroup} instances that have been folly
- * handled (whether successful or not).
- *
- * @return The number of {@link TaskGroup} instances that have been folly
- * handled (whether successful or not).
- */
- public long getCompletedTaskGroupCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.taskGroupCount;
- }
- }
-
- /**
- * Gets the number of task groups that have been successfully completed.
- *
- * @return The number of task groups that have been successfully completed.
- */
- public long getSuccessfulTaskGroupCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.groupSuccessCount;
- }
- }
-
- /**
- * Gets the number of task groups that have been completed with failures.
- *
- * @return The number of task groups that have been completed with failures.
- */
- public long getFailedTaskGroupCount() {
- synchronized (this.getStatisticsMonitor()) {
- return this.groupFailureCount;
- }
- }
-
- /**
- * The greatest number of tasks in the completed task groups. This returns
- * null if no task groups have been completed.
- *
- * @return The greatest number of tasks in the completed task groups, or
- * null if no task groups have been completed.
- */
- public Integer getGreatestTaskGroupSize() {
- synchronized (this.getStatisticsMonitor()) {
- if (this.greatestGroupSize <= 0) {
- return null;
- }
- return this.greatestGroupSize;
- }
- }
-
- /**
- * Gets the ratio of the total handling time across all threads to the
- * total active handling of the task scheduler to indicate the level
- * of parallelism achieved. This returns null if no tasks have
- * yet been handled.
- *
- * @return The ratio of the total handling time across all threads to the
- * total active handling time of the task scheduler, or
- * null if no tasks have been handled.
- */
- public Double getParallelism() {
- synchronized (this.getStatisticsMonitor()) {
- String timerKey = activelyHandling.toString();
- Long activeTime = this.timers.getElapsedTime(timerKey);
- if (activeTime == 0L) {
- return null;
- }
- Double totalTime = (double) this.totalHandlingTime;
- return (totalTime / ((double) activeTime));
- }
- }
+ /**
+ * The number of task groups that have completed with failures.
+ */
+ private long groupFailureCount = 0L;
- /**
- * Gets the greatest number of tasks that have been postponed.
- *
- * @return The greatest number of tasks that have been postponed.
- */
- public int getGreatestPostponedCount() {
- return this.greatestPostponedCount;
- }
+ /**
+ * The number of {@link ScheduledTask} instances handled. Each
+ * {@link ScheduledTask} may be backed by multiple duplicate actual {@link Task}
+ * instances.
+ */
+ private long handleCount = 0L;
- /**
- * Encapsulates a scheduled {@link Task} and all duplicates of that {@link
- * Task} assuming the tasks can be collapsed.
- *
- */
- protected static class ScheduledTask {
/**
- * The original backing task ID.
+ * The number of non-follow-up {@link ScheduledTask} instances handled. Each
+ * {@link ScheduledTask} may be backed by multiple duplicate actual {@link Task}
+ * instances.
*/
- private long origTaskId;
+ private long standardHandleCount = 0L;
/**
- * Flag indicating if this contains follow-up tasks or non-follow-up
- * tasks.
+ * The number of follow-up {@link ScheduledTask} instances handled. Each
+ * {@link ScheduledTask} may be backed by multiple duplicate actual {@link Task}
+ * instances.
*/
- private boolean followUp;
+ private long followUpHandleCount = 0L;
/**
- * The external follow-up ID to reference the task in persistent storage.
+ * The number of {@link ScheduledTask} instances handled successfully. Each
+ * {@link ScheduledTask} may be backed by multiple duplicate actual {@link Task}
+ * instances.
*/
- private String followUpId;
+ private long handleSuccessCount = 0L;
/**
- * The follow-up multiplicity since the follow-up tasks lack backing tasks.
+ * The number of {@link ScheduledTask} instances handled unsuccessfully. Each
+ * {@link ScheduledTask} may be backed by multiple duplicate actual {@link Task}
+ * instances.
*/
- private Integer multiplicity = null;
+ private long handleFailureCount = 0L;
/**
- * The nanosecond when this scheduled task is considered to be expired.
+ * The greatest task multiplicity encountered for non-follow-up tasks.
*/
- private Long expirationNanos = null;
+ private int greatestMultiplicity = 0;
/**
- * The action associated with the associated tasks.
+ * The greatest task multiplicity encountered for follow-up tasks.
*/
- private String action;
+ private int greatestFollowUpMultiplicity = 0;
/**
- * The parameters for the associated tasks.
+ * The total number of times an attempt was made to dequeue a message and one
+ * was ready.
*/
- private SortedMapnull if unknown.
- */
- public ScheduledTask(String jsonText,
- String followUpId,
- int multiplicity,
- long expirationTime,
- long elapsedMillisSinceCreation) {
- this(Task.deserialize(jsonText,
- false,
- elapsedMillisSinceCreation));
-
- this.followUp = true;
- this.followUpId = followUpId;
- this.allowCollapse = false;
- this.multiplicity = multiplicity;
-
- // determine the expiration in a consistent manner
- long now = System.currentTimeMillis();
- long remainingNanos = (expirationTime - now) * ONE_MILLION;
- this.expirationNanos = System.nanoTime() + remainingNanos;
- }
-
- /**
- * Checks if the actual tasks backing this instance are follow-up tasks.
- * Either all the tasks are follow-up tasks or all are not
- * follow-up tasks.
- *
- * @return true if the tasks are follow-up tasks, otherwise
+ * @return true if this instance is still handling tasks, otherwise
* false.
+ *
*/
- public boolean isFollowUp() {
- return this.followUp;
+ protected synchronized boolean isHandlingTasks() {
+ return this.handlingTasks;
}
/**
- * This method always returns false if not a follow-up task.
- * If this is a follow-up task then this returns true if the
- * follow-up task is expired, otherwise false.
+ * The {@link Object} to synchronize on when computing and recording statistics
+ * in a thread-safe manner.
*
- * @return true if this is an expired follow-up task, otherwise
- * false.
+ * @return The {@link Object} to synchronize on when computing and recording
+ * statistics in a thread-safe manner.
*/
- public boolean isFollowUpExpired() {
- if (this.expirationNanos == null) {
- return false;
- }
- return System.nanoTime() > this.expirationNanos;
+ protected final Object getStatisticsMonitor() {
+ return this.statsMonitor;
}
/**
- * Updates the expiration time to the specified number of milliseconds
- * since the epoch in UTC time coordinates.
+ * Gets the concurrency of the scheduler -- this is the number of threads it
+ * will use to handle tasks. The returned value will be a positive number
+ * greater than or equal to one (1).
*
- * @param expiration The expiration time in number of milliseconds since
- * the epoch in UTC time coordinates.
+ * @return The concurrency of the scheduler (i.e.: the number of threads it will
+ * use to handle tasks).
*/
- public void setFollowUpExpiration(long expiration) {
- // determine the expiration in a consistent manner
- long now = System.currentTimeMillis();
- long remainingNanos = (expiration - now) * ONE_MILLION;
- this.expirationNanos = System.nanoTime() + remainingNanos;
+ public int getConcurrency() {
+ return this.concurrency;
}
/**
- * Obtains the external ID used to identify the deserialized follow-up
- * task in persistent storage. This should always return null
- * if {@link #isFollowUp()} is false. This may return
- * null if {@link #isFollowUp()} is true if the
- * external persistent storage mechanism does not require an external ID.
+ * Gets the default concurrency with which to initialize if one is not specified
+ * in the initialization configuration via the {@link #CONCURRENCY_KEY}
+ * initialization parameter. By default, this returns
+ * {@link #DEFAULT_CONCURRENCY}, but it may be overridden to return something
+ * more sensible for a derived implementation.
+ *
+ * @return The default concurrency with which to initialize.
*
- * @return The external ID used to identify the deserialized follow-up
- * task in persistent storage.
+ * @see #getConcurrency()
+ * @see #CONCURRENCY_KEY
+ * @see #DEFAULT_CONCURRENCY
*/
- public String getFollowUpId() {
- return this.followUpId;
+ public int getDefaultConcurrency() {
+ return DEFAULT_CONCURRENCY;
}
/**
- * Removes all backing tasks that have been flagged as aborted and
- * returns the remaining number of backing tasks. If no backing tasks
- * remain then this {@link ScheduledTask} should itself be aborted.
+ * Gets the number of milliseconds to sleep between checks on the locks required
+ * for tasks that have been postponed due to contention. This timeout is used
+ * when there are pending tasks that have been postponed due to contention.
*
- * @return The number of backing tasks that were removed because they were
- * aborted.
+ * @return The number of milliseconds to sleep between checks on the locks
+ * required for tasks that have been postponed due to contention.
*/
- public synchronized int removeAborted() {
- if (this.isFollowUp()) {
- return 0;
- }
-
- int removedCount = 0;
- Iteratortrue if the duplicate tasks can be collapsed with
- * the backing task from this instance, and false if
- * collapse is not allowed.
+ * @param lockingService The {@link LockingService} for this instance.
*/
- public boolean isAllowingCollapse() {
- return this.allowCollapse;
+ protected void setLockingService(LockingService lockingService) {
+ this.lockingService = lockingService;
}
/**
- * Gets the number of duplicate tasks identical to this one that were
- * scheduled prior to the task being handled.
+ * Gets the default {@link LockingService} class name with which to initialize
+ * the backing {@link LockingService} if one is not specified in the
+ * initialization configuration via the {@link #LOCKING_SERVICE_CLASS_KEY}
+ * initialization parameter. By default, this returns the
+ * {@link #DEFAULT_LOCKING_SERVICE_CLASS_NAME}, but it may be overridden to
+ * return something more sensible for a derived implementation.
+ *
+ * @return The default {@link LockingService} class name with which to
+ * initialize.
*
- * @return The number of duplicate tasks like
+ * @see #initLockingService(JsonObject)
+ * @see #getDefaultLockingServiceConfig()
+ * @see #LOCKING_SERVICE_CLASS_KEY
+ * @see #LOCKING_SERVICE_CONFIG_KEY
+ * @see #DEFAULT_LOCKING_SERVICE_CLASS_NAME
*/
- public int getMultiplicity() {
- if (this.multiplicity != null) {
- return this.multiplicity;
- } else {
- return this.backingTasks.size();
- }
+ public String getDefaultLockingServiceClassName() {
+ return DEFAULT_LOCKING_SERVICE_CLASS_NAME;
}
/**
- * Marks all the backing tasks to transition to the {@link
- * Task.State#STARTED} state via {@link Task#beginHandling()}.
+ * Gets the default {@link JsonObject} configuration with which to initialize
+ * the backing {@link LockingService} if one is not specified in the
+ * initialization configuration via the {@link #LOCKING_SERVICE_CONFIG_KEY}
+ * initialization parameter. By default, this returns the null, but
+ * it may be overridden to return something more sensible for a derived
+ * implementation.
+ *
+ * @return The default {@link JsonObject} configuration with which to initialize
+ * the backing {@link LockingService}.
+ *
+ * @see #initLockingService(JsonObject)
+ * @see #getDefaultLockingServiceClassName()
+ * @see #LOCKING_SERVICE_CLASS_KEY
+ * @see #LOCKING_SERVICE_CONFIG_KEY
+ * @see #DEFAULT_LOCKING_SERVICE_CLASS_NAME
*/
- public void beginHandling() {
- this.backingTasks.forEach((task) -> {
- task.beginHandling();
- });
+ public JsonObject getDefaultLockingServiceConfig() {
+ return null;
}
/**
- * Marks this instance and the backing tasks as having succeeded.
+ * {@inheritDoc}
*/
- public void succeeded() {
- this.successful = Boolean.TRUE;
- this.backingTasks.forEach((task) -> {
- task.succeeded();
- });
+ @Override
+ public Scheduler createScheduler(boolean followUp) {
+ if (followUp) {
+ return new DefaultScheduler(this);
+ } else {
+ TaskGroup taskGroup = new TaskGroup();
+ return new DefaultScheduler(this, taskGroup);
+ }
}
/**
- * Marks this instance and the backing tasks as having failed.
+ * Creates a {@link Scheduler} for creating follow-up tasks to the specified
+ * {@link ScheduledTask} unless follow-up tasks are not supported for the
+ * specified {@link ScheduledTask}. The default implementation will always
+ * create a {@link DefaultScheduler} that will not have an associated
+ * {@link TaskGroup}.
*
- * @param failure The exception that occurred.
+ * @param task The {@link ScheduledTask} for which to create the follow-up
+ * scheduler.
+ * @return The follow-up {@link Scheduler} or null if follow-up
+ * tasks are not allowed for the specified {@link ScheduledTask}.
*/
- public void failed(Exception failure) {
- this.successful = Boolean.FALSE;
- this.backingTasks.forEach((task) -> {
- task.failed(failure);
- });
+ protected Scheduler createFollowUpScheduler(ScheduledTask task) {
+ // create a follow-up scheduler
+ return new DefaultScheduler(this);
}
/**
- * Checks if this {@link ScheduledTask} has been flagged as successful.
- * This returns null if the {@link ScheduledTask} has not
- * yet been handled, otherwise it returns {@link Boolean#TRUE} or {@link
- * Boolean#FALSE}.
+ * Schedules the tasks in the specified {@link List}.
+ *
+ * @param tasks The {@link List} of {@link Task} instances.
*
- * @return {@link Boolean#TRUE} if successful, {@link Boolean#FALSE} if
- * unsuccessful, and null if not yet completed.
+ * @throws ServiceExecutionException If a failure occurs in scheduling the
+ * tasks. If a failure occurs then it should
+ * be assumed that the tasks will not be
+ * handled and the associated message should
+ * be retried later.
*/
- public Boolean isSuccessful() {
- return this.successful;
+ protected void scheduleTasks(Listtrue.
+ * Dequeues a previously enqueued {@link ScheduledTask}.
*
- * @param lockingService The {@link LockingService} to use.
+ * @return The {@link ScheduledTask} that was dequeued.
+ */
+ protected synchronized ScheduledTask dequeueTask() {
+ this.timerPause(dequeueBlocking);
+ this.timerStart(dequeueTaskWaitLoop);
+
+ // set the hit flag to true
+ boolean hit = true;
+
+ int prevPendingCount = -1;
+ int prevPostponedCount = -1;
+
+ // wait for a task to be available
+ while (this.getState().isAvailable() && (this.pendingTasks.size() == 0) && (!this.isFollowUpReadyCheckTime())
+ && (!this.isPostponedReadyCheckTime())) {
+ // if we get here then no task was ready so we have a miss
+ hit = false;
+
+ // toggle the timers
+ this.toggleActiveAndWaitingTimers(this.pendingTasks.size(), this.postponedTasks.size(),
+ this.workerPool.isBusy());
+
+ // determine if postponed tasks exist
+ boolean postponed = (this.getPostponedTaskCount() > 0);
+
+ // determine how long to wait
+ long timeout = (postponed) ? Math.min(this.getPostponedTimeout(), this.getStandardTimeout())
+ : this.getStandardTimeout();
+
+ // wait for the designated duration
+ this.timerStart(dequeueTaskWait);
+ try {
+ logDebug("SLEEPING BEFORE RETRIEVING " + (postponed ? "POSTPONED" : "FOLLOW-UP") + " TASK: " + timeout);
+ this.wait(timeout);
+
+ } catch (InterruptedException ignore) {
+ // ignore the interruption
+ } finally {
+ this.timerPause(dequeueTaskWait);
+ }
+ }
+ this.timerPause(dequeueTaskWaitLoop);
+
+ // grab a postponed task if available
+ ScheduledTask task = null;
+ TaskType taskType = null;
+ int taskTypeCount = this.taskTypeOrder.size();
+ for (int index = 0; index < taskTypeCount && task == null; index++) {
+ taskType = this.taskTypeOrder.get(this.taskTypeIndex++);
+ this.taskTypeIndex = this.taskTypeIndex % taskTypeCount;
+ switch (taskType) {
+ case PENDING:
+ this.timerStart(checkPending);
+ try {
+ task = this.getReadyPendingTask();
+ } catch (Exception e) {
+ logWarning(e, "FAILED TO OBTAIN A TASK FROM THE PENDING QUEUE");
+ } finally {
+ this.timerPause(checkPending);
+ }
+ break;
+
+ case POSTPONED:
+ this.timerStart(checkPostponed);
+ try {
+ task = this.getReadyPostponedTask();
+ } catch (Exception e) {
+ logWarning(e, "FAILED TO OBTAIN A POSTPONED TASK, " + "DEFERRING POSTPONED TASKS FOR NOW");
+ } finally {
+ this.timerPause(checkPostponed);
+ }
+ break;
+
+ case FOLLOW_UP:
+ this.timerStart(checkFollowUp);
+ try {
+ task = this.getReadyFollowUpTask();
+ } catch (ServiceExecutionException e) {
+ logWarning(e, "FAILED TO OBTAIN A FOLLOW-UP TASK, " + "DEFERRING FOLLOW-UP TASKS FOR NOW");
+ } finally {
+ this.timerPause(checkFollowUp);
+ }
+ break;
+
+ default:
+ throw new IllegalStateException("Unrecognized task type: " + taskType);
+ }
+ }
+
+ // if not null then return the task
+ if (task != null) {
+ // ensure the timers toggled correctly
+ this.timerPause(waitingOnPostponed, waitingForTasks);
+ this.timerStart(activelyHandling);
+ this.updateDequeueHitRatio(hit);
+
+ // update the state
+ if (this.getState() == READY) {
+ this.setState(ACTIVE);
+ }
+
+ // check if we need to remove from the collapse lookup
+ if (!task.isFollowUp() && task.isAllowingCollapse()) {
+ ScheduledTask collapse = this.taskCollapseLookup.remove(task.getSignature());
+ if (task != collapse) {
+ throw new IllegalStateException("Collapse lookup table did not contain the same task as was "
+ + "dequeued. expected=[ " + task + " ], actual=[ " + collapse + " ]");
+ }
+ }
+
+ // return the task for handling
+ return task;
+ }
+
+ this.toggleActiveAndWaitingTimers(this.pendingTasks.size(), this.postponedTasks.size(),
+ this.workerPool.isBusy());
+ this.updateDequeueHitRatio(false);
+
+ // update the state
+ if ((this.getState() == ACTIVE) && (this.pendingTasks.size() == 0) && (this.postponedTasks.size() == 0)
+ && (!this.workerPool.isBusy())) {
+ // no pending or postponed tasks, no tasks being handled and we have none
+ // to return the user (e.g.: follow-up tasks), go from ACTIVE to READY
+ this.setState(READY);
+
+ } else if (this.getState() == READY) {
+ // we are either busy handling tasks or we have pending or postponed tasks
+ // and we are in the READY state so transition to ACTIVE
+ this.setState(ACTIVE);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a {@link ScheduledTask} from the pending queue that is ready for
+ * handling. This method will find the least-recently-scheduled task whose set
+ * of affected resources (identified by {@link ResourceKey} instances) could be
+ * locked without blocking and locks those resources. If no such pending task
+ * could be found then null is returned.
*
- * @return true if all required locks were obtained, otherwise
- * false.
+ * @return The next pending {@link ScheduledTask} that is now ready to try, or
+ * null if none are ready to try.
*/
- public synchronized boolean acquireLocks(LockingService lockingService) {
- if (this.lockToken != null) {
- return true;
- }
+ protected synchronized ScheduledTask getReadyPendingTask() {
+ this.timerStart(dequeueCheckLocked);
+ try {
+ // if none ready then check if we can grab a pending task
+ while (this.pendingTasks.size() > 0) {
+ // get the candidate task
+ ScheduledTask task = this.pendingTasks.remove(0);
+
+ // check if the task is aborted
+ if (this.skipIfAborted(task)) {
+ continue;
+ }
+
+ // attempt to lock the task resources
+ this.timerStart(obtainLocks);
+ boolean locked = task.acquireLocks(this.getLockingService());
+ this.timerPause(obtainLocks);
+
+ // if the lock was obtained, return the task
+ if (locked) {
+ return task;
+ }
- Setnull so that the previously postponed tasks are not checked for
+ * readiness too frequently. Otherwise, this method will find the least recently
+ * postponed {@link ScheduledTask} whose set of affected resources (identified
+ * by {@link ResourceKey} instances) are not currently locked. If there are no
+ * postponed {@link ScheduledTask} instance that meet the readiness criteria,
+ * then null is returned.
*
- * @param lockingService The {@link LockingService} with which to release
- * the locks.
+ * @return The next postponed {@link ScheduledTask} that is now ready to try.
*/
- public synchronized void releaseLocks(LockingService lockingService) {
- if (this.lockToken == null) {
- return;
- }
+ protected synchronized ScheduledTask getReadyPostponedTask() {
+ // get the elapsed time and update the timestamp
+ long now = System.nanoTime();
+ long elapsedNanos = now - this.postponedNanoTime;
+ long elapsedMillis = elapsedNanos / ONE_MILLION;
+
+ // check the timestamp
+ if (elapsedMillis < this.getPostponedTimeout()) {
+ return null;
+ }
+
+ // check if there are no postponed messages
+ if (this.postponedTasks.size() == 0) {
+ // since we have checked all the postponed messages (none) and none are
+ // ready then we need to update the timestamp
+ this.postponedNanoTime = now;
+
+ return null;
+ }
- Settrue if the specified
+ * {@link ScheduledTask} can be fully removed from the queue and ignored (i.e.:
+ * it has no more backing tasks). If not all backing tasks are aborted, then
+ * false is returned to indicate the task still needs to be
+ * handled.
+ *
+ * @param task The {@link ScheduledTask} to check if fully aborted and remove
+ * aborted tasks from.
+ * @return true if the specified {@link ScheduledTask} should be
+ * skipped because it is fully aborted, otherwise false.
+ */
+ protected boolean skipIfAborted(ScheduledTask task) {
+ // remove any aborted tasks
+ int abortCount = task.removeAborted();
+ this.taskAbortCount += abortCount;
+
+ // check if aborted
+ if (task.getMultiplicity() == 0) {
+ if (task.isAllowingCollapse()) {
+ ScheduledTask collapse = this.taskCollapseLookup.get(task.getSignature());
+ if (collapse == task) {
+ this.taskCollapseLookup.remove(task.getSignature());
+ } else {
+ throw new IllegalStateException("Unexpected collapsing task in lookup. expected=[ " + task
+ + " ], found=[ " + collapse + " ]");
+ }
+ }
+ return true;
+ }
+
+ // return false if we get here
+ return false;
+ }
/**
- * The {@link Timers} associated with the handling of the associated task.
+ * Checks if a check should be performed against the readiness of the postponed
+ * tasks. This returns true if and only if there is at least one
+ * postponed task and the readiness check has not been performed within the
+ * configured postponed timeout.
+ *
+ * @return true if it is time to perform a postponed task readiness
+ * check, otherwise false.
*/
- private Timers timers;
+ protected synchronized boolean isPostponedReadyCheckTime() {
+ // no need to do a ready check if no postponed messages
+ if (this.postponedTasks.size() == 0) {
+ return false;
+ }
+
+ // get the elapsed time and update the timestamp
+ long now = System.nanoTime();
+ long elapsedNanos = now - this.postponedNanoTime;
+ long elapsedMillis = elapsedNanos / ONE_MILLION;
+
+ // check the timestamp
+ return (elapsedMillis >= this.getPostponedTimeout());
+ }
/**
- * Constructs with the specified parameters.
- *
- * @param task The {@link Task} that was handled.
- * @param timers The {@link Timers} for handling the task.
+ * Returns a previously scheduled follow-up {@link ScheduledTask} that is now
+ * ready to be processed. If the resources that must be locked are not available
+ * for the follow-up task then it is left on the queue. If the last time this
+ * method was called was less than the {@linkplain #getPostponedTimeout()
+ * postpone timeout} then this method returns null so that the
+ * previously postponed tasks are not checked for readiness too frequently.
+ * Otherwise, this method will find the least recently postponed
+ * {@link ScheduledTask} whose set of affected resources (identified by
+ * {@link ResourceKey} instances) are not currently locked. If there are no
+ * postponed {@link ScheduledTask} instance that meet the readiness criteria,
+ * then null is returned.
+ *
+ * @return The next postponed {@link ScheduledTask} that is now ready to try.
+ * @throws ServiceExecutionException If a failure occurs in obtaining a
+ * follow-up task.
*/
- public TaskResult(ScheduledTask task, Timers timers) {
- this.task = task;
- this.timers = timers;
+ protected synchronized ScheduledTask getReadyFollowUpTask() throws ServiceExecutionException {
+ // get the current timestamp
+ long now = System.nanoTime();
+
+ // check if there are no follow-up messages
+ if (this.followUpTasks.size() <= 1) {
+ // we have no follow-up tasks in the cache, let's get some
+ Listtrue if and only if there is at least one
+ * follow-up task and the readiness check has not been performed within the
+ * configured follow-up timeout.
+ *
+ * @return true if it is time to perform a postponed task readiness
+ * check, otherwise false.
*/
- public ScheduledTask getTask() {
- return this.task;
+ protected synchronized boolean isFollowUpReadyCheckTime() {
+ // get the elapsed time and update the timestamp
+ long now = System.nanoTime();
+ long elapsedNanos = now - this.followUpNanoTime;
+ long elapsedMillis = elapsedNanos / ONE_MILLION;
+
+ // check the timestamp
+ return (elapsedMillis >= (this.getFollowUpDelay() / 2));
}
/**
- * Gets the associated {@link Timers}.
- *
- * @return The associated {@link Timers}.
+ * Enqueues the specified follow-up {@link Task} instance and persists it for
+ * future retrieval. A follow-up {@link Task} does not belong to a
+ * {@link TaskGroup} and therefore should have a null
+ * {@linkplain Task#getTaskGroup() task group property}.
+ *
+ * @param task The follow-up {@link Task} to enqueue.
+ *
+ * @throws IllegalArgumentException If any of the specified {@link Task}
+ * belongs to a {@link TaskGroup}.
+ *
+ * @throws ServiceExecutionException If a failure occurs in persisting the
+ * specified {@link Task} instances
+ *
+ */
+ protected abstract void enqueueFollowUpTask(Task task) throws ServiceExecutionException;
+
+ /**
+ * Retrieves a number of follow-up tasks from persistent storage. This should
+ * mark the retrieved tasks as pending and should not return them again until
+ * at least after {@link #getFollowUpTimeout()} milliseconds has past.
+ *
+ * @param count The suggested number of follow-up tasks to retrieve from
+ * persistent storage.
+ *
+ * @return The {@link List} of follow-up {@link Task} instances retrieved from
+ * persistent storage.
+ *
+ * @throws ServiceExecutionException If a failure occurs in persisting the
+ * specified {@link Task} instances
*/
- public Timers getTimers() {
- return this.timers;
+ protected abstract Listnull if no
+ * result was returned.
+ */
+ protected void handleAsyncResult(AsyncResulttrue if the worker pool is busy, otherwise
+ * false.
+ */
+ protected void toggleActiveAndWaitingTimers(int pendingCount, int postponedCount, boolean busy) {
+ synchronized (this.getStatisticsMonitor()) {
+ // check if there are messages
+ if (busy) {
+ this.timerPause(waitingForTasks, waitingOnPostponed);
+ this.timerStart(activelyHandling);
+
+ } else if (pendingCount == 0 && postponedCount == 0) {
+ // no tasks pending or postponed
+ this.timerPause(activelyHandling, waitingOnPostponed);
+ this.timerStart(waitingForTasks);
+
+ } else if (pendingCount > 0) {
+ // messages pending
+ this.timerPause(waitingForTasks, waitingOnPostponed);
+ this.timerStart(activelyHandling);
+
+ } else if (postponedCount > 0) {
+ // none pending, but some postponed
+ this.timerPause(activelyHandling, waitingForTasks);
+ this.timerStart(waitingOnPostponed);
+ }
+ }
+ }
+
+ /**
+ * Resumes the associated {@link Timers} in a thread-safe manner.
+ *
+ * @param statistic The {@link Stat} to resume.
+ * @param addlTimers The additional {@link Stat} instances to resume.
+ */
+ protected void timerResume(Stat statistic, Stat... addlTimers) {
+ String[] names = this.convertTimerKeys(addlTimers);
+ synchronized (this.getStatisticsMonitor()) {
+ if (names == null) {
+ this.timers.resume(statistic.toString());
+ } else {
+ this.timers.resume(statistic.toString(), names);
+ }
+ }
+ }
+
+ /**
+ * Starts the associated {@link Timers} in a thread-safe manner.
+ *
+ * @param statistic The
+ * {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat}
+ * to start.
+ * @param addlTimers The additional {@link Stat} instances to start.
+ */
+ protected void timerStart(Stat statistic, Stat... addlTimers) {
+ String[] names = this.convertTimerKeys(addlTimers);
+ synchronized (this.getStatisticsMonitor()) {
+ if (names == null) {
+ this.timers.start(statistic.toString());
+ } else {
+ this.timers.start(statistic.toString(), names);
+ }
+ }
+ }
+
+ /**
+ * Pauses the associated {@link Timers} in a thread-safe manner.
+ *
+ * @param statistic The {@link Stat} to pause.
+ * @param addlTimers The additional {@link Stat} instances to pause.
+ */
+ protected void timerPause(Stat statistic, Stat... addlTimers) {
+ String[] names = this.convertTimerKeys(addlTimers);
+ synchronized (this.getStatisticsMonitor()) {
+ if (names == null) {
+ this.timers.pause(statistic.toString());
+ } else {
+ this.timers.pause(statistic.toString(), names);
+ }
+ }
+ }
+
+ /**
+ * Gets the {@link Map} of
+ * {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat} keys
+ * to their {@link Number} values in an atomic thread-safe manner.
+ *
+ * @return The {@link Map} of
+ * {@link com.senzing.listener.communication.AbstractMessageConsumer.Stat}
+ * keys to their {@link Number} values.
+ */
+ @Override
+ public Mapnull if no non-follow-up
+ * tasks have been handled.
+ *
+ * @return The average task compression from collapsing non-follow-up tasks
+ * handled by the scheduling service, or null if no
+ * non-follow-up tasks have been handled.
+ */
+ public Double getAverageCompressionRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.standardHandleCount == 0) {
+ return null;
+ }
+ double completeCount = (double) this.taskCompleteCount;
+ double handleCount = (double) this.standardHandleCount;
+ return completeCount / handleCount;
+ }
+ }
+
+ /**
+ * Gets the greatest task compression from collapsing non-follow-up tasks
+ * handled by the scheduling service. This returns null if no tasks
+ * have been handled.
+ *
+ * @return The greatest task compression from collapsing non-follow-up tasks
+ * handled by the scheduling service, or null if no tasks
+ * have been handled.
+ */
+ public Integer getGreatestCompressionRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.greatestMultiplicity <= 0) {
+ return null;
+ }
+ return this.greatestMultiplicity;
+ }
+ }
+
+ /**
+ * Gets the average task compression from collapsing follow-up tasks handled by
+ * the scheduling service. This returns null if no follow-up tasks
+ * have been handled.
+ *
+ * @return The average task compression from collapsing follow-up tasks handled
+ * by the scheduling service, or null if no follow-up tasks
+ * have been handled.
+ */
+ public Double getAverageFollowUpCompressionRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.followUpHandleCount == 0) {
+ return null;
+ }
+ double completeCount = (double) this.followUpCompleteCount;
+ double handleCount = (double) this.followUpHandleCount;
+ return completeCount / handleCount;
+ }
+ }
+
+ /**
+ * Gets the greatest task compression from collapsing follow-up tasks handled by
+ * the scheduling service. This returns null if no follow-up tasks
+ * have been handled.
+ *
+ * @return The greatest task compression from collapsing follow-up tasks handled
+ * by the scheduling service, or null if no follow-up tasks
+ * have been handled.
+ */
+ public Integer getGreatestFollowUpCompressionRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.greatestFollowUpMultiplicity <= 0) {
+ return null;
+ }
+ return this.greatestFollowUpMultiplicity;
+ }
+ }
+
+ /**
+ * Gets the average number of tasks in all the completed task groups. This
+ * returns null if no task groups have been completed.
+ *
+ * @return The average number of tasks in all the completed task groups, or
+ * null if no task groups have been completed.
+ */
+ public Double getAverageTaskGroupSize() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.taskGroupCount == 0) {
+ return null;
+ }
+ double completeCount = (double) this.taskCompleteCount;
+ double groupCount = (double) this.taskGroupCount;
+ return (completeCount / groupCount);
+ }
+ }
+
+ /**
+ * Gets the dequeue hit ratio. This returns null if there have been
+ * no attempts to dequeue a task.
+ *
+ * @return The dequeue hit ratio, or null if no attempts have been
+ * made to dequeue a task.
+ */
+ public Double getDequeueHitRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if ((this.dequeueHitCount + this.dequeueMissCount) == 0) {
+ return null;
+ }
+ double hits = (double) this.dequeueHitCount;
+ double misses = (double) this.dequeueMissCount;
+ double total = hits + misses;
+ return (hits / total);
+ }
+ }
+
+ /**
+ * Call this to increment the number of times dequeue has been called with or
+ * without a task ready to be dequeued. This function is thread-safe with
+ * respect to other statistics.
+ *
+ * @param hit true if we have a "hit" and there is a task ready to
+ * be dequeued, otherwise false for a "miss".
+ */
+ protected void updateDequeueHitRatio(boolean hit) {
+ synchronized (this.getStatisticsMonitor()) {
+ if (hit) {
+ this.dequeueHitCount++;
+ } else {
+ this.dequeueMissCount++;
+ }
+ }
+ }
+
+ /**
+ * The average time in milliseconds that non-follow-up tasks have taken from
+ * scheduling until completion. This returns null if no
+ * non-follow-up tasks have been handled.
+ *
+ * @return The average time in milliseconds that non-follow-up tasks have taken
+ * from scheduling until completion, or null if no
+ * non-follow-up tasks have been handled.
+ */
+ public Double getAverageTaskTime() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.taskCompleteCount == 0) {
+ return null;
+ }
+ double totalTime = (double) this.totalTaskTime;
+ double completeCount = (double) this.taskCompleteCount;
+ return totalTime / completeCount;
+ }
+ }
+
+ /**
+ * The longest time in milliseconds that a non-follow-up task has taken from
+ * scheduling until completion. This returns null if no
+ * non-follow-up tasks have been handled.
+ *
+ * @return The longest time in milliseconds that a non-follow-up task has taken
+ * from scheduling until completion, or null if no
+ * non-follow-up tasks have been handled.
+ */
+ public Long getLongestTaskTime() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.longestTaskTime < 0) {
+ return null;
+ }
+ return this.longestTaskTime;
+ }
+ }
+
+ /**
+ * Gets the average number of milliseconds from all task groups to be handled
+ * from the time first task in the group was scheduled until the last task was
+ * completed. This returns null if no task groups have been
+ * completed.
+ *
+ * @return The average number of milliseconds from all task groups to be handled
+ * from the time first task in the group was scheduled until the last
+ * task was completed, or null if no task groups have been
+ * completed.
+ */
+ public Double getAverageTaskGroupTime() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.taskGroupCount == 0) {
+ return null;
+ }
+ double totalTime = (double) this.totalTaskGroupTime;
+ double groupCount = (double) this.taskGroupCount;
+ return totalTime / groupCount;
+ }
+ }
+
+ /**
+ * Gets the greatest number of milliseconds for a task groups to be handled from
+ * the time first task in the group was scheduled until the last task was
+ * completed. This returns null if no task groups have been
+ * completed.
+ *
+ * @return The greatest number of milliseconds for a task groups to be handled
+ * from the time first task in the group was scheduled until the last
+ * task was completed, or null if no task groups have been
+ * completed.
+ */
+ public Long getLongestTaskGroupTime() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.longestTaskGroupTime < 0L) {
+ return null;
+ }
+ return this.longestTaskGroupTime;
+ }
+ }
+
+ /**
+ * Gets the number of non-follow-up tasks that have been completed.
+ *
+ * @return The number of non-follow-up tasks that have been completed.
+ */
+ public long getCompletedTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.taskCompleteCount;
+ }
+ }
+
+ /**
+ * Gets the number of non-follow-up tasks that have been completed successfully.
+ *
+ * @return The number of non-follow-up tasks that have been completed
+ * successfully.
+ */
+ public long getSuccessfulTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.taskSuccessCount;
+ }
+ }
+
+ /**
+ * Gets the number of non-follow-up tasks that have been completed
+ * unsuccessfully (i.e.: with failures).
+ *
+ * @return The number of non-follow-up tasks that have been completed
+ * unsuccessfully (i.e.: with failures).
+ */
+ public long getFailedTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.taskFailureCount;
+ }
+ }
+
+ /**
+ * Gets the number of non-follow-up tasks that were aborted.
+ *
+ * @return The number of non-follow-up tasks that were aborted.
+ */
+ public long getAbortedTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.taskAbortCount;
+ }
+ }
+
+ /**
+ * Gets the number of follow-up tasks that have been completed.
+ *
+ * @return The number of follow-up tasks that have been completed.
+ */
+ public long getCompletedFollowUpCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.followUpCompleteCount;
+ }
+ }
+
+ /**
+ * Gets the number of follow-up tasks that have been completed successfully.
+ *
+ * @return The number of follow-up tasks that have been completed successfully.
+ */
+ public long getSuccessfulFollowUpCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.followUpSuccessCount;
+ }
+ }
+
+ /**
+ * Gets the number of follow-up tasks that have been completed unsuccessfully
+ * (i.e.: with failures).
+ *
+ * @return The number of follow-up tasks that have been completed successfully
+ * (i.e.: with failures).
+ */
+ public long getFailedFollowUpCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.followUpFailureCount;
+ }
+ }
+
+ /**
+ * Get the average number of milliseconds spent calling
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} for tasks (both
+ * follow-up and non-follow-up). If no tasks have been handled then
+ * null is returned.
+ *
+ * @return The average number of milliseconds spent calling
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} for
+ * tasks, or null if no tasks have been handled.
+ */
+ public Double getAverageHandleTaskTime() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.handleCount == 0) {
+ return null;
+ }
+ double totalTime = ((double) this.totalHandlingTime);
+ double callCount = ((double) this.handleCount);
+ return totalTime / callCount;
+ }
+ }
+
+ /**
+ * Get the total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
+ * to handle tasks (both follow-up and non-follow-up).
+ *
+ * @return The total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been
+ * called to handle tasks (both follow-up and non-follow-up).
+ */
+ public long getHandleTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.handleCount;
+ }
+ }
+
+ /**
+ * Get the total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
+ * to handle tasks successfully (both follow-up and non-follow-up).
+ *
+ * @return The total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been
+ * called to handle tasks successfully (both follow-up and
+ * non-follow-up).
+ */
+ public long getSuccessfulHandleTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.handleSuccessCount;
+ }
+ }
+
+ /**
+ * Get the total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
+ * to handle tasks unsuccessfully (both follow-up and non-follow-up).
+ *
+ * @return The total number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been
+ * called to handle tasks unsuccessfully (both follow-up and
+ * non-follow-up).
+ */
+ public long getFailedHandleTaskCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.handleFailureCount;
+ }
+ }
+
+ /**
+ * Gets the ratio of the number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been called
+ * to handle follow-up tasks to the number of times it has been called to handle
+ * all tasks that have been handled. This returns null if no
+ * tasks have been handled.
+ *
+ * @return The ratio of the number of times
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} has been
+ * called to handle follow-up tasks to the number of times it has been
+ * called to handle all tasks that have been handled, or
+ * null if no tasks have been handled.
+ */
+ public Double getFollowUpHandleTaskRatio() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.handleCount == 0) {
+ return null;
+ }
+ double followUp = ((double) this.followUpHandleCount);
+ double all = ((double) this.handleCount);
+ return followUp / all;
+ }
+ }
+
+ /**
+ * Gets the number of {@link TaskGroup} instances that have been folly handled
+ * (whether successful or not).
+ *
+ * @return The number of {@link TaskGroup} instances that have been folly
+ * handled (whether successful or not).
+ */
+ public long getCompletedTaskGroupCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.taskGroupCount;
+ }
+ }
+
+ /**
+ * Gets the number of task groups that have been successfully completed.
+ *
+ * @return The number of task groups that have been successfully completed.
+ */
+ public long getSuccessfulTaskGroupCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.groupSuccessCount;
+ }
+ }
+
+ /**
+ * Gets the number of task groups that have been completed with failures.
+ *
+ * @return The number of task groups that have been completed with failures.
+ */
+ public long getFailedTaskGroupCount() {
+ synchronized (this.getStatisticsMonitor()) {
+ return this.groupFailureCount;
+ }
+ }
+
+ /**
+ * The greatest number of tasks in the completed task groups. This returns
+ * null if no task groups have been completed.
+ *
+ * @return The greatest number of tasks in the completed task groups, or
+ * null if no task groups have been completed.
+ */
+ public Integer getGreatestTaskGroupSize() {
+ synchronized (this.getStatisticsMonitor()) {
+ if (this.greatestGroupSize <= 0) {
+ return null;
+ }
+ return this.greatestGroupSize;
+ }
+ }
+
+ /**
+ * Gets the ratio of the total handling time across all threads to the total
+ * active handling of the task scheduler to indicate the level of parallelism
+ * achieved. This returns null if no tasks have yet been handled.
+ *
+ * @return The ratio of the total handling time across all threads to the total
+ * active handling time of the task scheduler, or null if
+ * no tasks have been handled.
+ */
+ public Double getParallelism() {
+ synchronized (this.getStatisticsMonitor()) {
+ String timerKey = activelyHandling.toString();
+ Long activeTime = this.timers.getElapsedTime(timerKey);
+ if (activeTime == 0L) {
+ return null;
+ }
+ Double totalTime = (double) this.totalHandlingTime;
+ return (totalTime / ((double) activeTime));
+ }
+ }
+
+ /**
+ * Gets the greatest number of tasks that have been postponed.
+ *
+ * @return The greatest number of tasks that have been postponed.
+ */
+ public int getGreatestPostponedCount() {
+ return this.greatestPostponedCount;
+ }
+
+ /**
+ * Encapsulates a scheduled {@link Task} and all duplicates of that {@link Task}
+ * assuming the tasks can be collapsed.
+ *
+ */
+ protected static class ScheduledTask {
+ /**
+ * The original backing task ID.
+ */
+ private long origTaskId;
+
+ /**
+ * Flag indicating if this contains follow-up tasks or non-follow-up tasks.
+ */
+ private boolean followUp;
+
+ /**
+ * The external follow-up ID to reference the task in persistent storage.
+ */
+ private String followUpId;
+
+ /**
+ * The follow-up multiplicity since the follow-up tasks lack backing tasks.
+ */
+ private Integer multiplicity = null;
+
+ /**
+ * The nanosecond when this scheduled task is considered to be expired.
+ */
+ private Long expirationNanos = null;
+
+ /**
+ * The action associated with the associated tasks.
+ */
+ private String action;
+
+ /**
+ * The parameters for the associated tasks.
+ */
+ private SortedMapnull if
+ * unknown.
+ */
+ public ScheduledTask(String jsonText, String followUpId, int multiplicity, long expirationTime, long elapsedMillisSinceCreation) {
+ this(Task.deserialize(jsonText, false, elapsedMillisSinceCreation));
+
+ this.followUp = true;
+ this.followUpId = followUpId;
+ this.allowCollapse = false;
+ this.multiplicity = multiplicity;
+
+ // determine the expiration in a consistent manner
+ long now = System.currentTimeMillis();
+ long remainingNanos = (expirationTime - now) * ONE_MILLION;
+ this.expirationNanos = System.nanoTime() + remainingNanos;
+ }
+
+ /**
+ * Checks if the actual tasks backing this instance are follow-up tasks. Either
+ * all the tasks are follow-up tasks or all are not follow-up tasks.
+ *
+ * @return true if the tasks are follow-up tasks, otherwise
+ * false.
+ */
+ public boolean isFollowUp() {
+ return this.followUp;
+ }
+
+ /**
+ * This method always returns false if not a follow-up task. If
+ * this is a follow-up task then this returns true if the follow-up
+ * task is expired, otherwise false.
+ *
+ * @return true if this is an expired follow-up task, otherwise
+ * false.
+ */
+ public boolean isFollowUpExpired() {
+ if (this.expirationNanos == null) {
+ return false;
+ }
+ return System.nanoTime() > this.expirationNanos;
+ }
+
+ /**
+ * Updates the expiration time to the specified number of milliseconds since the
+ * epoch in UTC time coordinates.
+ *
+ * @param expiration The expiration time in number of milliseconds since the
+ * epoch in UTC time coordinates.
+ */
+ public void setFollowUpExpiration(long expiration) {
+ // determine the expiration in a consistent manner
+ long now = System.currentTimeMillis();
+ long remainingNanos = (expiration - now) * ONE_MILLION;
+ this.expirationNanos = System.nanoTime() + remainingNanos;
+ }
+
+ /**
+ * Obtains the external ID used to identify the deserialized follow-up task in
+ * persistent storage. This should always return null if
+ * {@link #isFollowUp()} is false. This may return
+ * null if {@link #isFollowUp()} is true if the
+ * external persistent storage mechanism does not require an external ID.
+ *
+ * @return The external ID used to identify the deserialized follow-up task in
+ * persistent storage.
+ */
+ public String getFollowUpId() {
+ return this.followUpId;
+ }
+
+ /**
+ * Removes all backing tasks that have been flagged as aborted and returns the
+ * remaining number of backing tasks. If no backing tasks remain then this
+ * {@link ScheduledTask} should itself be aborted.
+ *
+ * @return The number of backing tasks that were removed because they were
+ * aborted.
+ */
+ public synchronized int removeAborted() {
+ if (this.isFollowUp()) {
+ return 0;
+ }
+
+ int removedCount = 0;
+ Iteratortrue if the duplicate tasks can be collapsed with the
+ * backing task from this instance, and false if collapse
+ * is not allowed.
+ */
+ public boolean isAllowingCollapse() {
+ return this.allowCollapse;
+ }
+
+ /**
+ * Gets the number of duplicate tasks identical to this one that were scheduled
+ * prior to the task being handled.
+ *
+ * @return The number of duplicate tasks like
+ */
+ public int getMultiplicity() {
+ if (this.multiplicity != null) {
+ return this.multiplicity;
+ } else {
+ return this.backingTasks.size();
+ }
+ }
+
+ /**
+ * Marks all the backing tasks to transition to the {@link Task.State#STARTED}
+ * state via {@link Task#beginHandling()}.
+ */
+ public void beginHandling() {
+ this.backingTasks.forEach((task) -> {
+ task.beginHandling();
+ });
+ }
+
+ /**
+ * Marks this instance and the backing tasks as having succeeded.
+ */
+ public void succeeded() {
+ this.successful = Boolean.TRUE;
+ this.backingTasks.forEach((task) -> {
+ task.succeeded();
+ });
+ }
+
+ /**
+ * Marks this instance and the backing tasks as having failed.
+ *
+ * @param failure The exception that occurred.
+ */
+ public void failed(Exception failure) {
+ this.successful = Boolean.FALSE;
+ this.backingTasks.forEach((task) -> {
+ task.failed(failure);
+ });
+ }
+
+ /**
+ * Checks if this {@link ScheduledTask} has been flagged as successful. This
+ * returns null if the {@link ScheduledTask} has not yet been
+ * handled, otherwise it returns {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ *
+ * @return {@link Boolean#TRUE} if successful, {@link Boolean#FALSE} if
+ * unsuccessful, and null if not yet completed.
+ */
+ public Boolean isSuccessful() {
+ return this.successful;
+ }
+
+ /**
+ * Acquires the locks on the resources required for this instance. If no locks
+ * are required this simply returns true.
+ *
+ * @param lockingService The {@link LockingService} to use.
+ *
+ * @return true if all required locks were obtained, otherwise
+ * false.
+ */
+ public synchronized boolean acquireLocks(LockingService lockingService) {
+ if (this.lockToken != null) {
+ return true;
+ }
+
+ Setnull if the task is a follow-up task either
* being constructed directly or deserialized from the
* database.
- * @param allowCollapse true if collapsing identical tasks of
- * this type is allowed, otherwise false.
- */
- Task(String action,
- SortedMaptrue if collapsing identical tasks of this
+ * type is allowed, otherwise false.
+ */
+ Task(String action, SortedMaptrue if collapsing
- * identical tasks of
- * this type is allowed, otherwise
- * false.
+ * identical tasks of this type is
+ * allowed, otherwise false.
* @param elapsedMillisSinceSerialization The number of milliseconds that have
* elapsed since the deserialized task
* was originally created, or
* null if unknown.
*/
- private Task(String action,
- SortedMapnull if the task has no group.
+ * Gets the associated {@link TaskGroup}, if any. This returns null
+ * if the task has no group.
*
- * @return The associated {@link TaskGroup}, or null if this
- * task has no group.
+ * @return The associated {@link TaskGroup}, or null if this task
+ * has no group.
*/
public TaskGroup getTaskGroup() {
return this.taskGroup;
}
/**
- * Checks if this task can be collapsed with other collapsible tasks that
- * are identical to it for a single call to {@link
- * TaskHandler#handleTask(String, Map, int, Scheduler)} with an incrementally
- * increased multiplicity.
+ * Checks if this task can be collapsed with other collapsible tasks that are
+ * identical to it for a single call to
+ * {@link TaskHandler#handleTask(String, Map, int, Scheduler)} with an
+ * incrementally increased multiplicity.
*
* @return true if this task can be collapsed, otherwise
* false.
@@ -459,8 +439,8 @@ public boolean isAllowingCollapse() {
}
/**
- * Checks if this {@link Task} instance has been marked in a state of
- * completion either {@link State#SUCCESSFUL}, {@link State#FAILED} or
+ * Checks if this {@link Task} instance has been marked in a state of completion
+ * either {@link State#SUCCESSFUL}, {@link State#FAILED} or
* {@link State#ABORTED}.
*
* @return true if completed, otherwise false.
@@ -579,27 +559,26 @@ public SortedSetnull
- * if the {@link Task} has not yet been handled or was handled but completed
+ * while handling this {@link Task}. This method returns null if
+ * the {@link Task} has not yet been handled or was handled but completed
* successfully.
*
- * @return The {@link Exception} describing any failure that may have
- * occurred while handling this {@link Task}, or null
- * if the {@link Task} has not yet been handled or was handled but
- * completed successfully.
+ * @return The {@link Exception} describing any failure that may have occurred
+ * while handling this {@link Task}, or null if the
+ * {@link Task} has not yet been handled or was handled but completed
+ * successfully.
*/
public Exception getFailure() {
return this.failure;
}
/**
- * Converts the specified {@link Task} to a {@link JsonObjectBuilder}
- * describing the action, parameters and associated resource keys.
+ * Converts the specified {@link Task} to a {@link JsonObjectBuilder} describing
+ * the action, parameters and associated resource keys.
*
* @param task The {@link Task} to be represented as a {@link JsonObject}.
*
- * @return The {@link JsonObjectBuilder} describing the specified
- * {@link Task}.
+ * @return The {@link JsonObjectBuilder} describing the specified {@link Task}.
*/
public static JsonObjectBuilder toJsonObjectBuilder(Task task) {
JsonObjectBuilder job1 = Json.createObjectBuilder();
@@ -622,11 +601,10 @@ public static JsonObjectBuilder toJsonObjectBuilder(Task task) {
}
/**
- * Converts this {@link Task} instance to a {@link JsonObjectBuilder}
- * describing the action, parameters and associated resource keys.
+ * Converts this {@link Task} instance to a {@link JsonObjectBuilder} describing
+ * the action, parameters and associated resource keys.
*
- * @return The {@link JsonObjectBuilder} describing this {@link Task}
- * instance.
+ * @return The {@link JsonObjectBuilder} describing this {@link Task} instance.
*/
public JsonObjectBuilder toJsonObjectBuilder() {
return toJsonObjectBuilder(this);
@@ -655,8 +633,8 @@ public JsonObject toJsonObject() {
}
/**
- * Converts the specified {@link Task} to JSON text describing the
- * action, parameters and associated resource keys.
+ * Converts the specified {@link Task} to JSON text describing the action,
+ * parameters and associated resource keys.
*
* @param task The {@link Task} to be represented as a {@link JsonObject}.
*
@@ -676,32 +654,32 @@ public String toJsonText() {
}
/**
- * Gets a message digest signature which can be used to easily identify
- * a serialized text representation of this task.
+ * Gets a message digest signature which can be used to easily identify a
+ * serialized text representation of this task.
*
* @param task The {@link Task} to convert to a signature.
*
- * @return A message digest signature which can be used to easily identify
- * this a serialized text representation of this task.
+ * @return A message digest signature which can be used to easily identify this
+ * a serialized text representation of this task.
*/
public static String toSignature(Task task) {
return toSignature(toJsonText(task));
}
/**
- * Gets a message digest signature which can be used to easily identify
- * a serialized text representation of this task.
+ * Gets a message digest signature which can be used to easily identify a
+ * serialized text representation of this task.
*
- * @return A message digest signature which can be used to easily identify
- * this a serialized text representation of this task.
+ * @return A message digest signature which can be used to easily identify this
+ * a serialized text representation of this task.
*/
public String getSignature() {
return toSignature(this);
}
/**
- * Converts the specified JSON text to a message digest signature which can
- * be used to easily identify the specified serialized JSON text.
+ * Converts the specified JSON text to a message digest signature which can be
+ * used to easily identify the specified serialized JSON text.
*
* @param jsonText The JSON text representation of the task.
*
@@ -709,13 +687,12 @@ public String getSignature() {
*/
private static String toSignature(String jsonText) {
try {
+ HexFormat hex = HexFormat.of();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(jsonText.getBytes(UTF_8));
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
- for (byte b : digest) {
- sb.append(Integer.toHexString(0xFF & b));
- }
+ sb.append(hex.formatHex(digest));
return sb.toString();
} catch (NoSuchAlgorithmException cannotHappen) {
@@ -736,8 +713,8 @@ public String toString() {
}
/**
- * Provided as a way to convert to a {@link String} without
- * synchronization to avoid possible dead locks.
+ * Provided as a way to convert to a {@link String} without synchronization to
+ * avoid possible dead locks.
*
* @param state The {@link Task.State} known to the caller.
*
@@ -752,16 +729,15 @@ protected String toString(State state) {
String signature = toSignature(jsonText);
sb.append("signature=[ ").append(signature).append(" ], ");
- sb.append("allowCollapse=[ ").append(this.isAllowingCollapse())
- .append(" ], ").append("task=[ ").append(jsonText).append(" ]");
+ sb.append("allowCollapse=[ ").append(this.isAllowingCollapse()).append(" ], ").append("task=[ ").append(jsonText)
+ .append(" ]");
return sb.toString();
}
/**
- * Gets the number of milliseconds from the point in time at which this task
- * was created until it was scheduled to be handled. If the task has not
- * yet been scheduled then the number of milliseconds since it was created
- * is returned.
+ * Gets the number of milliseconds from the point in time at which this task was
+ * created until it was scheduled to be handled. If the task has not yet been
+ * scheduled then the number of milliseconds since it was created is returned.
*
* @return The duration of the unscheduled time of this task in milliseconds.
*/
@@ -774,13 +750,12 @@ public synchronized long getUnscheduledTime() {
}
/**
- * Gets the number of milliseconds from the point in time at which this task
- * was scheduled until handling of the task was started. If the task has not
- * yet been scheduled then negative one (-1) is returned. If the task has
- * been scheduled, but has not yet been started then the number of
- * milliseconds since it was scheduled is returned. If the task was a
- * deserialized follow-up task then this may include the time spent in
- * persistent storage.
+ * Gets the number of milliseconds from the point in time at which this task was
+ * scheduled until handling of the task was started. If the task has not yet
+ * been scheduled then negative one (-1) is returned. If the task has been
+ * scheduled, but has not yet been started then the number of milliseconds since
+ * it was scheduled is returned. If the task was a deserialized follow-up task
+ * then this may include the time spent in persistent storage.
*
* @return The duration of the pending time of this task in milliseconds.
*/
@@ -798,11 +773,11 @@ public synchronized long getPendingTime() {
}
/**
- * Gets the number of milliseconds from the point in time at which handling
- * of this task was started until handling completed successfully or with
- * failures. If the task has not yet been started then negative one (-1)
- * is returned. If the task started, but has not yet completed then the
- * number of milliseconds since it was started is returned.
+ * Gets the number of milliseconds from the point in time at which handling of
+ * this task was started until handling completed successfully or with failures.
+ * If the task has not yet been started then negative one (-1) is returned. If
+ * the task started, but has not yet completed then the number of milliseconds
+ * since it was started is returned.
*
* @return The duration of the handling time of this task in milliseconds.
*/
@@ -818,15 +793,14 @@ public synchronized long getHandlingTime() {
}
/**
- * Gets the number of milliseconds from the point in time from when this
- * task was scheduled until the time it was completed either successfully or
- * with failures (or aborted). If not yet completed then the number of
- * milliseconds since the task was created is returned. If the task was a
- * deserialized follow-up task then this may include the time spent in
- * persistent storage.
+ * Gets the number of milliseconds from the point in time from when this task
+ * was scheduled until the time it was completed either successfully or with
+ * failures (or aborted). If not yet completed then the number of milliseconds
+ * since the task was created is returned. If the task was a deserialized
+ * follow-up task then this may include the time spent in persistent storage.
*
- * @return The duration of the time from scheduling to completion of this
- * task in milliseconds.
+ * @return The duration of the time from scheduling to completion of this task
+ * in milliseconds.
*/
public synchronized long getRoundTripTime() {
long result = this.elapsedMillisSinceSerialization;
@@ -839,11 +813,11 @@ public synchronized long getRoundTripTime() {
}
/**
- * Gets the number of milliseconds from the point in time from when this
- * task was created until the time it was completed either successfully or
- * with failures. If not yet completed then the number of milliseconds since
- * the task was created is returned. If the task was a deserialized follow-up
- * task then this may include the time spent in persistent storage.
+ * Gets the number of milliseconds from the point in time from when this task
+ * was created until the time it was completed either successfully or with
+ * failures. If not yet completed then the number of milliseconds since the task
+ * was created is returned. If the task was a deserialized follow-up task then
+ * this may include the time spent in persistent storage.
*
* @return The duration of the lifespan of this task in milliseconds.
*/
diff --git a/src/test/java/com/senzing/datamart/RabbitMqUriTest.java b/src/test/java/com/senzing/datamart/RabbitMqUriTest.java
index b6fb458..23b8822 100644
--- a/src/test/java/com/senzing/datamart/RabbitMqUriTest.java
+++ b/src/test/java/com/senzing/datamart/RabbitMqUriTest.java
@@ -1312,9 +1312,11 @@ public void testEqualsAndHashWithVirtualHostAndQuery(
uri2 = new RabbitMqUri(
secure2, user2, password2, host2, virtualHost2, queryOptions2);
- assertNotEquals(uri1, uri2, "Objects are unexpectedly not equal");
+ assertNotEquals(uri1, uri2, "Objects are unexpectedly equal: "
+ + "uri1=[ " + uri1 + " ], uri2=[ " + uri2 + " ]");
assertNotEquals(uri1.hashCode(), uri2.hashCode(),
- "Objects unexpectedly have different hash codes");
+ "Objects unexpectedly have the same hash code: uri1=[ "
+ + uri1 + " ], uri2=[ " + uri2 + " ]");
} catch (Exception e) {
fail("Failed test with exception.", e);
diff --git a/src/test/java/com/senzing/datamart/SQLiteUriTest.java b/src/test/java/com/senzing/datamart/SQLiteUriTest.java
index 762a3fd..30ad5a3 100644
--- a/src/test/java/com/senzing/datamart/SQLiteUriTest.java
+++ b/src/test/java/com/senzing/datamart/SQLiteUriTest.java
@@ -314,7 +314,7 @@ public void testParse(String unusedUser, String unusedPassword, File file, Map previousAffectedSet = null;
- private int noOverlapCount = 0;
-
- private Set