diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c735354c..e56c1e5fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ **Details** - nflow-engine: + - Use more optimal SQL when polling workflows when database supports update returning syntax - Only rollback poll operation when no workflows could be allocated for executing (when multiple pollers compete for same workflows) + - Allow configuring executor queue length with _nflow.dispatcher.executor.queue.size_ - nflow-rest: - Add support for user-provided action description when updating a workflow instance diff --git a/DECISION_LOG.md b/DECISION_LOG.md index e3f4462c7..8816a5b7d 100644 --- a/DECISION_LOG.md +++ b/DECISION_LOG.md @@ -2,6 +2,11 @@ (newest first) +2015-01-10 gmokki, efonsell +--------------------------- +When polling for next workflow instances in WorkflowInstanceDao, the modified field in OptimisticLockKey is handled as String instead of Timestamp to avoid problems caused by losing millisecond precision from timestamps in some cases (for example with some older versions of MySQL). + + 2014-12-10 eputtone ------------------- Internal nFlow functionalities can access DAO layer (package com.nitorcreations.nflow.engine.internal.dao) directly instead of going through service layer (package com.nitorcreations.nflow.engine.service). Rationale: service layer is currently part of public API that we wish to keep as simple as possible. Example: WorkflowDefinitionResource in nflow-rest-api uses WorkflowDefinitionDao directly for retrieving StoredWorkflowDefinitions, because we don't want to confuse public API users with multiple workflow definition representations. diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/config/EngineConfiguration.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/config/EngineConfiguration.java index eff6bd7e0..724f19d4f 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/config/EngineConfiguration.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/config/EngineConfiguration.java @@ -25,9 +25,10 @@ public class EngineConfiguration { public WorkflowInstanceExecutor nflowExecutor(@NFlow ThreadFactory nflowThreadFactory, Environment env) { int threadCount = env.getProperty("nflow.executor.thread.count", Integer.class, 2 * getRuntime().availableProcessors()); int awaitTerminationSeconds = env.getProperty("nflow.dispatcher.await.termination.seconds", Integer.class, 60); - int notifyThreshold = env.getProperty("nflow.dispatcher.executor.queue.wait_until_threshold", Integer.class, 0); + int queueSize = env.getProperty("nflow.dispatcher.executor.queue.size", Integer.class, 2 * threadCount); + int notifyThreshold = env.getProperty("nflow.dispatcher.executor.queue.wait_until_threshold", Integer.class, queueSize / 2); int keepAliveSeconds = env.getProperty("nflow.dispatcher.executor.thread.keepalive.seconds", Integer.class, 0); - return new WorkflowInstanceExecutor(threadCount, notifyThreshold, awaitTerminationSeconds, keepAliveSeconds, + return new WorkflowInstanceExecutor(queueSize, threadCount, notifyThreshold, awaitTerminationSeconds, keepAliveSeconds, nflowThreadFactory); } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/dao/WorkflowInstanceDao.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/dao/WorkflowInstanceDao.java index ece371b83..04167cd25 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/dao/WorkflowInstanceDao.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/dao/WorkflowInstanceDao.java @@ -13,7 +13,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -43,6 +42,7 @@ import org.springframework.transaction.annotation.Transactional; import com.nitorcreations.nflow.engine.internal.config.NFlow; +import com.nitorcreations.nflow.engine.internal.storage.db.SQLVariants; import com.nitorcreations.nflow.engine.workflow.definition.StateExecutionStatistics; import com.nitorcreations.nflow.engine.workflow.instance.QueryWorkflowInstances; import com.nitorcreations.nflow.engine.workflow.instance.WorkflowInstance; @@ -67,9 +67,15 @@ public class WorkflowInstanceDao { private JdbcTemplate jdbc; private NamedParameterJdbcTemplate namedJdbc; ExecutorDao executorInfo; + private SQLVariants sqlVariants; private long workflowInstanceQueryMaxResults; private long workflowInstanceQueryMaxResultsDefault; + @Inject + public void setSQLVariants(SQLVariants sqlVariants) { + this.sqlVariants = sqlVariants; + } + @Inject public void setJdbcTemplate(@NFlow JdbcTemplate nflowJdbcTemplate) { this.jdbc = nflowJdbcTemplate; @@ -209,12 +215,26 @@ public void processRow(ResultSet rs) throws SQLException { @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC_ANON", justification = "common jdbctemplate practice") @Transactional public List pollNextWorkflowInstanceIds(int batchSize) { + if (sqlVariants.hasUpdateReturning()) { + return pollNextWorkflowInstanceIdsUsingUpdateReturning(batchSize); + } + return pollNextWorkflowInstanceIdsUsingSelectUpdate(batchSize); + } + + private List pollNextWorkflowInstanceIdsUsingUpdateReturning(int batchSize) { + return jdbc.queryForList("update nflow_workflow set executor_id = " + executorInfo.getExecutorId() + + " where id in (select id from nflow_workflow where executor_id is null and next_activation < current_timestamp and " + + executorInfo.getExecutorGroupCondition() + " order by next_activation asc limit " + batchSize + + ") and executor_id is null returning id", Integer.class); + } + + private List pollNextWorkflowInstanceIdsUsingSelectUpdate(int batchSize) { String sql = "select id, modified from nflow_workflow where executor_id is null and next_activation < current_timestamp and " + executorInfo.getExecutorGroupCondition() + " order by next_activation asc limit " + batchSize; List instances = jdbc.query(sql, new RowMapper() { @Override public OptimisticLockKey mapRow(ResultSet rs, int rowNum) throws SQLException { - return new OptimisticLockKey(rs.getInt("id"), rs.getTimestamp("modified")); + return new OptimisticLockKey(rs.getInt("id"), rs.getString("modified")); } }); Collections.sort(instances); @@ -247,11 +267,11 @@ public OptimisticLockKey mapRow(ResultSet rs, int rowNum) throws SQLException { private static class OptimisticLockKey implements Comparable { public final int id; - public final Timestamp modified; + public final String modified; - public OptimisticLockKey(int id, Timestamp modified) { + public OptimisticLockKey(int id, String string) { this.id = id; - this.modified = modified; + this.modified = string; } @Override diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcher.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcher.java index 3c5936d4b..24eb7172c 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcher.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcher.java @@ -1,6 +1,5 @@ package com.nitorcreations.nflow.engine.internal.executor; -import static java.lang.Math.max; import static org.slf4j.LoggerFactory.getLogger; import java.util.List; @@ -107,7 +106,7 @@ private void dispatch(List nextInstanceIds) { } private List getNextInstanceIds() { - int nextBatchSize = max(0, 2 * executor.getMaximumPoolSize() - executor.getActiveCount()); + int nextBatchSize = executor.getQueueRemainingCapacity(); logger.debug("Polling next {} workflow instances.", nextBatchSize); return workflowInstances.pollNextWorkflowInstanceIds(nextBatchSize); } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutor.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutor.java index 4c7d4f600..1381af362 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutor.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutor.java @@ -1,6 +1,5 @@ package com.nitorcreations.nflow.engine.internal.executor; -import static java.lang.Integer.MAX_VALUE; import static java.lang.Thread.currentThread; import static java.util.concurrent.TimeUnit.SECONDS; import static org.slf4j.LoggerFactory.getLogger; @@ -18,9 +17,10 @@ public class WorkflowInstanceExecutor { final ThreadPoolExecutor executor; final ThresholdBlockingQueue queue; - public WorkflowInstanceExecutor(int threadCount, int notifyThreshold, int awaitTerminationSeconds, int keepAliveSeconds, + public WorkflowInstanceExecutor(int maxQueueSize, int threadCount, int notifyThreshold, int awaitTerminationSeconds, + int keepAliveSeconds, ThreadFactory threadFactory) { - queue = new ThresholdBlockingQueue<>(MAX_VALUE, notifyThreshold); + queue = new ThresholdBlockingQueue<>(maxQueueSize, notifyThreshold); executor = new ThreadPoolExecutor(threadCount, threadCount, keepAliveSeconds, SECONDS, queue, threadFactory); executor.allowCoreThreadTimeOut(keepAliveSeconds > 0); this.awaitTerminationSeconds = awaitTerminationSeconds; @@ -34,12 +34,8 @@ public void execute(Runnable runnable) { executor.execute(runnable); } - public int getMaximumPoolSize() { - return executor.getMaximumPoolSize(); - } - - public int getActiveCount() { - return executor.getActiveCount(); + public int getQueueRemainingCapacity() { + return queue.remainingCapacity(); } public void shutdown() { diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/H2DatabaseConfiguration.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/H2DatabaseConfiguration.java index a99c78e0f..32f0fee84 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/H2DatabaseConfiguration.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/H2DatabaseConfiguration.java @@ -45,5 +45,10 @@ public static class H2SQLVariants implements SQLVariants { public String currentTimePlusSeconds(int seconds) { return "dateadd('second', " + seconds + ", current_timestamp)"; } + + @Override + public boolean hasUpdateReturning() { + return false; + } } } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/MysqlDatabaseConfiguration.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/MysqlDatabaseConfiguration.java index b1f434ea9..ae92065a2 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/MysqlDatabaseConfiguration.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/MysqlDatabaseConfiguration.java @@ -63,5 +63,10 @@ public static class MySQLVariants implements SQLVariants { public String currentTimePlusSeconds(int seconds) { return "date_add(current_timestamp, interval " + seconds + " second)"; } + + @Override + public boolean hasUpdateReturning() { + return false; + } } } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/PgDatabaseConfiguration.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/PgDatabaseConfiguration.java index 35954d6cc..19085abd5 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/PgDatabaseConfiguration.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/PgDatabaseConfiguration.java @@ -22,5 +22,10 @@ public static class PostgreSQLVariants implements SQLVariants { public String currentTimePlusSeconds(int seconds) { return "current_timestamp + interval '" + seconds + " second'"; } + + @Override + public boolean hasUpdateReturning() { + return true; + } } } diff --git a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/SQLVariants.java b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/SQLVariants.java index 4fb5b76ab..38456e63d 100644 --- a/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/SQLVariants.java +++ b/nflow-engine/src/main/java/com/nitorcreations/nflow/engine/internal/storage/db/SQLVariants.java @@ -2,4 +2,6 @@ public interface SQLVariants { String currentTimePlusSeconds(int seconds); + + boolean hasUpdateReturning(); } diff --git a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/config/EngineConfigurationTest.java b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/config/EngineConfigurationTest.java index c2e2ab72b..dbee48e0c 100644 --- a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/config/EngineConfigurationTest.java +++ b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/config/EngineConfigurationTest.java @@ -27,9 +27,17 @@ public class EngineConfigurationTest { @InjectMocks private final EngineConfiguration configuration = new EngineConfiguration(); - public void dispatcherPoolExecutorInstantiation() { + @Test + public void dispatcherPoolExecutorInstantiationFromThreads() { + WorkflowInstanceExecutor executor = configuration.nflowExecutor(threadFactory, environment); + assertThat(executor.getQueueRemainingCapacity(), is(200)); + } + + @Test + public void dispatcherPoolExecutorInstantiationFromQueueSize() { + environment.setProperty("nflow.dispatcher.executor.queue.size", "10"); WorkflowInstanceExecutor executor = configuration.nflowExecutor(threadFactory, environment); - assertThat(executor.getMaximumPoolSize(), is(100)); + assertThat(executor.getQueueRemainingCapacity(), is(10)); } @Test diff --git a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcherTest.java b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcherTest.java index 273e76aee..ce523fc05 100644 --- a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcherTest.java +++ b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowDispatcherTest.java @@ -51,7 +51,7 @@ public void setup() { when(env.getProperty("nflow.dispatcher.sleep.ms", Long.class, 5000l)).thenReturn(0l); when(env.getProperty("nflow.dispatcher.executor.queue.wait_until_threshold", Integer.class, 0)).thenReturn(0); when(recovery.isTransactionSupportEnabled()).thenReturn(true); - executor = new WorkflowInstanceExecutor(2, 0, 10, 0, new CustomizableThreadFactory("nflow-executor-")); + executor = new WorkflowInstanceExecutor(3, 2, 0, 10, 0, new CustomizableThreadFactory("nflow-executor-")); dispatcher = new WorkflowDispatcher(executor, workflowInstances, executorFactory, recovery, env); } diff --git a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutorTest.java b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutorTest.java index 8377fc518..4ac9982f2 100644 --- a/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutorTest.java +++ b/nflow-engine/src/test/java/com/nitorcreations/nflow/engine/internal/executor/WorkflowInstanceExecutorTest.java @@ -27,7 +27,7 @@ public class WorkflowInstanceExecutorTest { @Test public void testThreadPoolCreateWithCorrectParameters() { - WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(2, 1, 3, 4, threadFactory); + WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(3, 2, 1, 3, 4, threadFactory); assertThat(t.executor.getCorePoolSize(), is(2)); assertThat(t.executor.getMaximumPoolSize(), is(2)); assertThat(t.executor.getKeepAliveTime(SECONDS), is(4L)); @@ -38,28 +38,27 @@ public void testThreadPoolCreateWithCorrectParameters() { @Test public void testDummyGetters() { - WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(2, 1, 3, 4, threadFactory); - assertThat(t.getActiveCount(), is(0)); - assertThat(t.getMaximumPoolSize(), is(2)); + WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(3, 2, 1, 3, 4, threadFactory); + assertThat(t.getQueueRemainingCapacity(), is(3)); } @Test public void testExecute() { - WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(2, 1, 3, 4, new CustomizableThreadFactory("test")); + WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(3, 2, 1, 3, 4, new CustomizableThreadFactory("test")); t.execute(runnable); verify(runnable, timeout(1000)).run(); } @Test public void testWait() throws InterruptedException { - WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(2, 1, 3, 4, new CustomizableThreadFactory("test")); + WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(3, 2, 1, 3, 4, new CustomizableThreadFactory("test")); t.execute(runnable); t.waitUntilQueueSizeLowerThanThreshold(new DateTime().plusSeconds(5)); } @Test public void testShutdown() { - WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(2, 1, 3, 4, new CustomizableThreadFactory("test")); + WorkflowInstanceExecutor t = new WorkflowInstanceExecutor(3, 2, 1, 3, 4, new CustomizableThreadFactory("test")); t.shutdown(); assertThat(t.executor.isShutdown(), is(true)); } diff --git a/nflow-jetty/pom.xml b/nflow-jetty/pom.xml index 9d0a0cc22..7eca6f3a9 100644 --- a/nflow-jetty/pom.xml +++ b/nflow-jetty/pom.xml @@ -34,11 +34,10 @@ nflow-explorer ${nflow.explorer.version} tar.gz - false - ${project.build.outputDirectory}/static/explorer + true + ${project.build.directory}/explorer - config.js @@ -65,7 +64,7 @@ maven-resources-plugin - copy-resources + copy-swagger-resources process-resources copy-resources @@ -84,6 +83,26 @@ + + copy-explorer-resources + process-resources + + copy-resources + + + ${project.build.outputDirectory}/static/explorer + false + + + ${project.build.directory}/explorer + false + + config.js + + + + + diff --git a/nflow-rest-api/src/main/java/com/nitorcreations/nflow/rest/v1/WorkflowInstanceResource.java b/nflow-rest-api/src/main/java/com/nitorcreations/nflow/rest/v1/WorkflowInstanceResource.java index f7012c988..3ec6e9e14 100644 --- a/nflow-rest-api/src/main/java/com/nitorcreations/nflow/rest/v1/WorkflowInstanceResource.java +++ b/nflow-rest-api/src/main/java/com/nitorcreations/nflow/rest/v1/WorkflowInstanceResource.java @@ -101,6 +101,9 @@ public Response updateWorkflowInstance( msg += "API changed nextActivationTime to " + req.nextActivationTime + "."; } } + if (msg.isEmpty()) { + return noContent().build(); + } WorkflowInstance instance = builder.build(); boolean updated = workflowInstances.updateWorkflowInstance(instance, new WorkflowInstanceAction.Builder(instance) .setStateText(trimToNull(msg)).setExecutionEnd(now()).build()); diff --git a/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/AbstractNflowTest.java b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/AbstractNflowTest.java index 5cc6a3da4..23612b0a2 100644 --- a/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/AbstractNflowTest.java +++ b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/AbstractNflowTest.java @@ -17,12 +17,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.nitorcreations.nflow.engine.workflow.statistics.Statistics; import com.nitorcreations.nflow.rest.v1.msg.Action; import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceRequest; import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceResponse; import com.nitorcreations.nflow.rest.v1.msg.ListWorkflowDefinitionResponse; import com.nitorcreations.nflow.rest.v1.msg.ListWorkflowInstanceResponse; +import com.nitorcreations.nflow.rest.v1.msg.StatisticsResponse; +import com.nitorcreations.nflow.rest.v1.msg.UpdateWorkflowInstanceRequest; import com.nitorcreations.nflow.tests.config.PropertiesConfiguration; import com.nitorcreations.nflow.tests.config.RestClientConfiguration; import com.nitorcreations.nflow.tests.runner.NflowServerRule; @@ -64,8 +65,12 @@ public void setStatisticsResource(@Named("statistics") WebClient client) { } protected ListWorkflowInstanceResponse getWorkflowInstance(int instanceId) { + return getInstanceResource(instanceId).get(ListWorkflowInstanceResponse.class); + } + + private WebClient getInstanceResource(int instanceId) { WebClient client = fromClient(workflowInstanceResource, true).path(Integer.toString(instanceId)); - return client.get(ListWorkflowInstanceResponse.class); + return client; } protected ListWorkflowDefinitionResponse[] getWorkflowDefinitions() { @@ -73,9 +78,9 @@ protected ListWorkflowDefinitionResponse[] getWorkflowDefinitions() { return client.get(ListWorkflowDefinitionResponse[].class); } - public Statistics getStatistics() { + public StatisticsResponse getStatistics() { WebClient client = fromClient(statisticsResource, true); - return client.get(Statistics.class); + return client.get(StatisticsResponse.class); } protected ListWorkflowInstanceResponse getWorkflowInstance(int id, String expectedState) throws InterruptedException { @@ -112,6 +117,10 @@ protected CreateWorkflowInstanceResponse createWorkflowInstance(CreateWorkflowIn return makeWorkflowInstanceQuery(request, CreateWorkflowInstanceResponse.class); } + protected String updateWorkflowInstance(int instanceId, UpdateWorkflowInstanceRequest request) { + return getInstanceResource(instanceId).put(request, String.class); + } + private T makeWorkflowInstanceQuery(CreateWorkflowInstanceRequest request, Class responseClass) { return fromClient(workflowInstanceResource, true).put(request, responseClass); } diff --git a/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/DemoWorkflowTest.java b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/DemoWorkflowTest.java index 127cdd609..29752bc30 100644 --- a/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/DemoWorkflowTest.java +++ b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/DemoWorkflowTest.java @@ -15,10 +15,10 @@ import org.junit.Test; import org.springframework.context.annotation.ComponentScan; -import com.nitorcreations.nflow.engine.workflow.statistics.Statistics; import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceRequest; import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceResponse; import com.nitorcreations.nflow.rest.v1.msg.ListWorkflowInstanceResponse; +import com.nitorcreations.nflow.rest.v1.msg.StatisticsResponse; import com.nitorcreations.nflow.tests.demo.DemoWorkflow; import com.nitorcreations.nflow.tests.runner.NflowServerRule; @@ -48,8 +48,8 @@ public void t01_startDemoWorkflow() { } public void t02_queryStatistics() { - Statistics statistics = getStatistics(); - assertThat(statistics.executionStatistics.count + statistics.queuedStatistics.count, greaterThan(0)); + StatisticsResponse statistics = getStatistics(); + assertThat(statistics.executionStatistics.count + statistics.queueStatistics.count, greaterThan(0)); } @Test(timeout = 5000) @@ -71,9 +71,9 @@ public void t03_queryDemoWorkflowHistory() throws Exception { } public void t04_queryStatistics() { - Statistics statistics = getStatistics(); + StatisticsResponse statistics = getStatistics(); assertEquals(0, statistics.executionStatistics.count); - assertEquals(0, statistics.queuedStatistics.count); + assertEquals(0, statistics.queueStatistics.count); } } diff --git a/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/FutureWorkflowTest.java b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/FutureWorkflowTest.java new file mode 100644 index 000000000..19d75300c --- /dev/null +++ b/nflow-tests/src/test/java/com/nitorcreations/nflow/tests/FutureWorkflowTest.java @@ -0,0 +1,99 @@ +package com.nitorcreations.nflow.tests; + +import static java.util.Arrays.asList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.cxf.jaxrs.client.WebClient.fromClient; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.joda.time.DateTime.now; +import static org.junit.Assert.assertThat; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +import javax.inject.Inject; + +import org.joda.time.DateTime; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.env.Environment; + +import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceRequest; +import com.nitorcreations.nflow.rest.v1.msg.CreateWorkflowInstanceResponse; +import com.nitorcreations.nflow.rest.v1.msg.ListWorkflowInstanceResponse; +import com.nitorcreations.nflow.rest.v1.msg.UpdateWorkflowInstanceRequest; +import com.nitorcreations.nflow.tests.demo.DemoWorkflow; +import com.nitorcreations.nflow.tests.runner.NflowServerRule; + +@FixMethodOrder(NAME_ASCENDING) +public class FutureWorkflowTest extends AbstractNflowTest { + static DateTime FUTURE = new DateTime(2038, 1, 1, 1, 2, 3, 321); + + @ClassRule + public static NflowServerRule server = new NflowServerRule.Builder().springContextClass(DemoConfiguration.class).build(); + + private static CreateWorkflowInstanceResponse resp; + + public FutureWorkflowTest() { + super(server); + } + + @ComponentScan(basePackageClasses = DemoWorkflow.class) + static class DemoConfiguration { + @Inject + public DemoConfiguration(Environment env) { + // mysql 5.5.x (from travis) and mariadb 10.0 (only when using mysql jdbc driver instead of mariadb jdbc driver) do not + // support millisecond precision in timestamps + if (asList(env.getActiveProfiles()).contains("nflow.db.mysql")) { + FUTURE = FUTURE.withMillisOfSecond(0); + } + } + } + + @Test + public void t01_scheduleDemoWorkflowToTomorrow() { + CreateWorkflowInstanceRequest req = new CreateWorkflowInstanceRequest(); + req.type = "demo"; + req.businessKey = "1"; + req.activationTime = FUTURE; + resp = fromClient(workflowInstanceResource, true).put(req, CreateWorkflowInstanceResponse.class); + assertThat(resp.id, notNullValue()); + } + + @Test + public void t02_verifyStatusNotStarted() throws InterruptedException { + SECONDS.sleep(10); + verifyWorkflowNotStarted(); + } + + @Test + public void t03_testNonUpdate() { + UpdateWorkflowInstanceRequest req = new UpdateWorkflowInstanceRequest(); + updateWorkflowInstance(resp.id, req); + verifyWorkflowNotStarted(); + } + + private void verifyWorkflowNotStarted() { + ListWorkflowInstanceResponse wf = getWorkflowInstance(resp.id); + assertThat(wf.started, nullValue()); + assertThat(wf.state, is(DemoWorkflow.State.begin.name())); + assertThat(wf.nextActivation.getMillis(), is(FUTURE.getMillis())); + } + + @Test + public void t04_scheduleToNow() { + UpdateWorkflowInstanceRequest req = new UpdateWorkflowInstanceRequest(); + req.nextActivationTime = now(); + updateWorkflowInstance(resp.id, req); + } + + @Test + public void t05_verifyWorkflowRuns() throws InterruptedException { + SECONDS.sleep(10); + ListWorkflowInstanceResponse wf = getWorkflowInstance(resp.id); + assertThat(wf.started, notNullValue()); + assertThat(wf.state, not(is(DemoWorkflow.State.begin.name()))); + } +} diff --git a/pom.xml b/pom.xml index ff372a0a5..3784c63d2 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,7 @@ 4.1.4.RELEASE 1.3.12 1.1.0.Final + 2.1 @@ -273,6 +274,14 @@ nexus-staging-maven-plugin ${nexus-staging-maven.version} + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + false + +