From 6064ba4a05d6120984d8d40fba112a857e6c1ddc Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 09:10:59 +0200 Subject: [PATCH 01/26] Refinement for "Update to Java 21" --- WHATSNEW.txt | 4 ++++ gradle.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WHATSNEW.txt b/WHATSNEW.txt index ad2d06a3..35d264db 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -1,3 +1,7 @@ +COPPER 6.0.0 +============ +- Breaking: Update to Java 21 + COPPER 5.5.2 ============ - Maintenance: Update spring 5.3.34 -> 5.3.36 diff --git a/gradle.properties b/gradle.properties index ee4feea5..48a5a531 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.5.3-SNAPSHOT +version=6.0.0-SNAPSHOT org.gradle.console=plain From 3e0c2532e8eae6b5226c83beb0a841a674bda580 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 11:20:14 +0200 Subject: [PATCH 02/26] Focus on core, jmx and ext --- build.gradle | 136 +--- .../src/main/java/module-info.java | 9 - .../cassandra/loadtest/DataCreator.java | 67 -- .../loadtest/DummyResponseSender.java | 54 -- .../cassandra/loadtest/EngineStarter.java | 35 - .../LoadTestCassandraEngineFactory.java | 75 -- .../cassandra/loadtest/LoadTestData.java | 35 - .../loadtest/PermanentLoadCreator.java | 102 --- .../cassandra/loadtest/TestMain.java | 53 -- .../loadtest/workflows/LoadTestWorkflow.java | 136 ---- .../src/main/resources/log4j.properties | 43 -- .../cassandra-getting-started.txt | 45 -- .../database/create-keyspace-cluster.cql | 2 - .../database/create-keyspace-singlenode.cql | 2 - .../database/truncate-all-tables.cql | 3 - .../src/main/java/module-info.java | 17 - .../cassandra/AlwaysRetryPolicy.java | 63 -- .../cassandra/CassandraEngineFactory.java | 140 ---- .../cassandra/CassandraOperation.java | 51 -- .../cassandra/CassandraSessionManager.java | 29 - .../CassandraSessionManagerImpl.java | 90 --- .../CassandraSessionManagerPojo.java | 57 -- .../cassandra/CassandraStorage.java | 546 --------------- .../core/persistent/cassandra/JsonMapper.java | 24 - .../persistent/cassandra/JsonMapperImpl.java | 46 -- .../cassandra/PojoCassandraEngineFactory.java | 34 - .../core/persistent/hybrid/CacheStats.java | 67 -- .../persistent/hybrid/CorrelationIdMap.java | 92 --- .../hybrid/DefaultTimeoutManager.java | 181 ----- .../persistent/hybrid/HybridDBStorage.java | 644 ------------------ .../hybrid/HybridDBStorageAccessor.java | 30 - .../hybrid/HybridEngineFactory.java | 77 --- .../hybrid/HybridTransactionController.java | 41 -- .../core/persistent/hybrid/QueueElement.java | 54 -- .../hybrid/QueueElementComparator.java | 37 - .../core/persistent/hybrid/Storage.java | 53 -- .../core/persistent/hybrid/StorageCache.java | 133 ---- .../persistent/hybrid/TimeoutManager.java | 35 - .../core/persistent/hybrid/TimeoutSlot.java | 43 -- .../persistent/hybrid/WorkflowInstance.java | 53 -- .../persistent/cassandra/copper-schema.cql | 53 -- .../src/test/java/module-info.test | 5 - .../CassandraEngineFactoryUsage.java | 39 -- .../CassandraSessionManagerImplTest.java | 33 - .../persistent/cassandra/CassandraTest.java | 74 -- .../cassandra/DummyResponseSender.java | 54 -- .../core/persistent/cassandra/TestData.java | 35 - .../cassandra/TestWorkflowCassandraTest.java | 68 -- .../UnitTestCassandraEngineFactory.java | 70 -- .../cassandra/workflows/TestWorkflow.java | 125 ---- .../hybrid/HybridDBStorageTest.java | 99 --- .../src/test/resources/log4j.properties | 41 -- .../test/resources/unittest-cassandra.yaml | 602 ---------------- .../PERFORMANCE_TEST_HOWTO.MD | 26 - .../src/main/java/module-info.java | 11 - .../performancetest/impl/MockAdapter.java | 147 ---- .../performancetest/main/ConfigParameter.java | 109 --- .../main/ConfigParameterGroup.java | 34 - .../main/ConfigurationManager.java | 80 --- .../main/DataSourceFactory.java | 57 -- .../main/LatencyPerformanceTest.java | 68 -- .../performancetest/main/Main.java | 59 -- .../main/PerformanceTestContext.java | 498 -------------- .../main/ThroughputPerformanceTest.java | 122 ---- .../workflows/SavepointPerfTestWorkflow.java | 60 -- .../workflows/WaitNotifyPerfTestWorkflow.java | 68 -- .../src/main/resources/log4j.properties | 40 -- .../performancetest.austermann.properties | 50 -- .../performancetest.cassandra.properties | 37 - .../performancetest.default.properties | 16 - .../performancetest.oracle.properties | 35 - .../regtest/audit/SpringAuditTrailTest.java | 34 - .../BasePersistentWorkflowTest.java | 112 --- .../BaseSpringTxnPersistentWorkflowTest.java | 82 --- .../DerbyDbPersistentWorkflowTest.java | 13 +- ...erbyDbSpringTxnPersistentWorkflowTest.java | 126 ---- .../persistent/H2PersistentWorkflowTest.java | 13 +- .../H2SpringTxnPersistentWorkflowTest.java | 128 ---- .../MySqlPersistentWorkflowTest.java | 12 +- .../MySqlSpringTxnPersistentWorkflowTest.java | 101 --- .../OraclePersistentWorkflowTest.java | 13 +- .../OracleSimplePersistentWorkflowTest.java | 13 +- ...OracleSpringTxnPersistentWorkflowTest.java | 116 ---- .../PostgreSQLPersistentWorkflowTest.java | 13 +- ...greSQLSpringTxnPersistentWorkflowTest.java | 102 --- .../SpringlessBasePersistentWorkflowTest.java | 24 +- .../persistent-engine-unittest-context.xml | 122 ---- .../PersistentUnitTestWorkflow.java | 1 - .../src/main/java/module-info.java | 16 - .../org/copperengine/spring/JmxExporter.java | 94 --- .../spring/SpringDependencyInjector.java | 64 -- .../spring/SpringEngineStarter.java | 44 -- .../spring/SpringTransaction.java | 46 -- .../spring/SpringTransactionController.java | 79 --- .../spring/audit/AuditTrailQueryEngine.java | 229 ------- .../spring/audit/SpringTxnAuditTrail.java | 72 -- .../copperengine/spring/audit/package.html | 26 - .../java/org/copperengine/spring/package.html | 26 - settings.gradle | 8 +- 99 files changed, 94 insertions(+), 7884 deletions(-) delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/module-info.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DataCreator.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DummyResponseSender.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/EngineStarter.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestCassandraEngineFactory.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestData.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/PermanentLoadCreator.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/TestMain.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/workflows/LoadTestWorkflow.java delete mode 100644 projects/copper-cassandra/cassandra-loadtest/src/main/resources/log4j.properties delete mode 100644 projects/copper-cassandra/cassandra-storage/cassandra-getting-started.txt delete mode 100644 projects/copper-cassandra/cassandra-storage/database/create-keyspace-cluster.cql delete mode 100644 projects/copper-cassandra/cassandra-storage/database/create-keyspace-singlenode.cql delete mode 100644 projects/copper-cassandra/cassandra-storage/database/truncate-all-tables.cql delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/module-info.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/AlwaysRetryPolicy.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactory.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraOperation.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManager.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImpl.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerPojo.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraStorage.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapper.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapperImpl.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/PojoCassandraEngineFactory.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CacheStats.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CorrelationIdMap.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/DefaultTimeoutManager.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorage.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorageAccessor.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridEngineFactory.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridTransactionController.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElement.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElementComparator.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/Storage.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/StorageCache.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutManager.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutSlot.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/WorkflowInstance.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/main/resources/org/copperengine/core/persistent/cassandra/copper-schema.cql delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/module-info.test delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactoryUsage.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImplTest.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraTest.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/DummyResponseSender.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestData.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestWorkflowCassandraTest.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/UnitTestCassandraEngineFactory.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/workflows/TestWorkflow.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/hybrid/HybridDBStorageTest.java delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/resources/log4j.properties delete mode 100644 projects/copper-cassandra/cassandra-storage/src/test/resources/unittest-cassandra.yaml delete mode 100644 projects/copper-performance-test/PERFORMANCE_TEST_HOWTO.MD delete mode 100644 projects/copper-performance-test/src/main/java/module-info.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/impl/MockAdapter.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameter.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameterGroup.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigurationManager.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/DataSourceFactory.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/LatencyPerformanceTest.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/Main.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/PerformanceTestContext.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ThroughputPerformanceTest.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/SavepointPerfTestWorkflow.java delete mode 100644 projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/WaitNotifyPerfTestWorkflow.java delete mode 100644 projects/copper-performance-test/src/main/resources/log4j.properties delete mode 100644 projects/copper-performance-test/src/main/resources/performancetest.austermann.properties delete mode 100644 projects/copper-performance-test/src/main/resources/performancetest.cassandra.properties delete mode 100644 projects/copper-performance-test/src/main/resources/performancetest.default.properties delete mode 100644 projects/copper-performance-test/src/main/resources/performancetest.oracle.properties delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/audit/SpringAuditTrailTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BaseSpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbSpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2SpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlSpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLSpringTxnPersistentWorkflowTest.java delete mode 100644 projects/copper-regtest/src/test/resources/SpringTxnPersistentWorkflowTest/persistent-engine-unittest-context.xml delete mode 100644 projects/copper-spring/src/main/java/module-info.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/JmxExporter.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/SpringDependencyInjector.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/SpringEngineStarter.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransaction.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransactionController.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/audit/AuditTrailQueryEngine.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/audit/SpringTxnAuditTrail.java delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/audit/package.html delete mode 100644 projects/copper-spring/src/main/java/org/copperengine/spring/package.html diff --git a/build.gradle b/build.gradle index fab88836..8217d652 100644 --- a/build.gradle +++ b/build.gradle @@ -305,7 +305,6 @@ project(':projects:copper-regtest') { dependencies { implementation project(':projects:copper-coreengine') - implementation project(':projects:copper-spring') implementation project(':projects:copper-ext') implementation "org.ow2.asm:asm:$asmVersion" @@ -328,91 +327,6 @@ project(':projects:copper-regtest') { } } -project(':projects:copper-ext') { - ext.moduleName = 'org.copperengine.ext' - dependencies { - implementation project(':projects:copper-coreengine') - - implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "commons-io:commons-io:$commonsIoVersion" - implementation "com.google.guava:guava:$guavaVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" - - testImplementation "org.slf4j:slf4j-api:$slf4jVersion" - //testImplementation 'org.apache.logging.log4j:log4j-core:2.+' - //testImplementation 'org.slf4j:slf4j-log4j12:2.+' - } -} - -project(':projects:copper-cassandra:cassandra-storage') { - ext.moduleName = 'org.copperengine.cassandra.storage' - dependencies { - implementation project(':projects:copper-coreengine') - implementation project(':projects:copper-ext') - - api "com.datastax.cassandra:cassandra-driver-core:$cassandraDriverVersion" - api "com.google.guava:guava:$guavaVersion" - implementation "org.slf4j:slf4j-api:$slf4jVersion" - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "commons-io:commons-io:$commonsIoVersion" - implementation "commons-lang:commons-lang:$commonsLangVersion" - implementation "com.fasterxml.jackson.core:jackson-core:$jacksonVersion" - implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" - - testImplementation("org.cassandraunit:cassandra-unit:$cassandraUnitVersion") { - exclude module: 'hamcrest-core' - exclude module: 'hamcrest-library' - exclude module: 'cassandra-thrift' - exclude module: 'high-scale-lib' - } - } -} - -project(':projects:copper-cassandra:cassandra-loadtest') { - apply plugin:'application' - ext.moduleName = 'org.copperengine.cassandra.loadtest' - - mainClassName = "org.copperengine.core.persistent.cassandra.loadtest.PermanentLoadCreator" - - dependencies { - implementation project(':projects:copper-coreengine') - implementation project(':projects:copper-ext') - implementation project(':projects:copper-cassandra:cassandra-storage') - - implementation "org.slf4j:slf4j-api:$slf4jVersion" - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" - } -} - - -project(':projects:copper-spring') { - ext.moduleName = 'org.copperengine.spring' - dependencies { - implementation project(':projects:copper-coreengine') - - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - - // Spring - implementation "org.springframework:spring-aop:$springVersion" - implementation "org.springframework:spring-beans:$springVersion" - implementation "org.springframework:spring-context:$springVersion" - implementation "org.springframework:spring-core:$springVersion" - implementation "org.springframework:spring-expression:$springVersion" - implementation "org.springframework:spring-jdbc:$springVersion" - implementation "org.springframework:spring-tx:$springVersion" - - implementation "org.springframework.batch:spring-batch-infrastructure:$springBatchVersion" - } -} - - project(':projects:copper-coreengine') { ext.moduleName = 'org.copperengine.core' dependencies { @@ -440,48 +354,24 @@ project(':projects:copper-coreengine') { } } -subprojects { - task allDeps(type: DependencyReportTask) {} -} - -project(':projects:copper-performance-test') { - ext.moduleName = 'org.copperengine.performancetest' +project(':projects:copper-ext') { + ext.moduleName = 'org.copperengine.ext' dependencies { implementation project(':projects:copper-coreengine') - implementation project(':projects:copper-ext') - implementation project(':projects:copper-cassandra:cassandra-storage') + implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" implementation "org.ow2.asm:asm:$asmVersion" implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" + implementation "commons-io:commons-io:$commonsIoVersion" implementation "com.google.guava:guava:$guavaVersion" - implementation "mysql:mysql-connector-java:$mysqlVersion" - implementation "org.apache.derby:derby:$derbyVersion" - implementation "postgresql:postgresql:$postgresqlVersion" - implementation "com.h2database:h2:$h2Version" - implementation "com.mchange:c3p0:$c3p0Version" - implementation "org.slf4j:slf4j-api:$slf4jVersion" - runtimeOnly fileTree(dir: "$rootDir/3rdPartyLibs", include: '*.jar') - } - - jar { - dependsOn ':projects:copper-coreengine:jar', ':projects:copper-ext:jar', ':projects:copper-cassandra:cassandra-storage:jar' - manifest.attributes provider: 'gradle' - - archiveName = "copper-performance-test.jar" - - from { - configurations.runtime.collect { - it.isDirectory() ? it : zipTree(it) - } - configurations.compile.collect { - it.isDirectory() ? it : zipTree(it) - } - } - - manifest { - attributes 'Main-Class': 'org.copperengine.performancetest.main.Main' - } + implementation "org.yaml:snakeyaml:$snakeyamlVersion" - } + testImplementation "org.slf4j:slf4j-api:$slf4jVersion" + //testImplementation 'org.apache.logging.log4j:log4j-core:2.+' + //testImplementation 'org.slf4j:slf4j-log4j12:2.+' + } } + +subprojects { + task allDeps(type: DependencyReportTask) {} +} \ No newline at end of file diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/module-info.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/module-info.java deleted file mode 100644 index b507e1d1..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/module-info.java +++ /dev/null @@ -1,9 +0,0 @@ -module org.copperengine.cassandra.loadtest { - requires org.copperengine.core; - requires org.copperengine.ext; - requires org.copperengine.cassandra.storage; - - requires java.sql; - - requires org.slf4j; -} \ No newline at end of file diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DataCreator.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DataCreator.java deleted file mode 100644 index a9164ca8..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DataCreator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.util.Collections; -import java.util.List; - -import org.copperengine.core.Workflow; -import org.copperengine.core.WorkflowInstanceDescr; -import org.copperengine.core.persistent.PersistentScottyEngine; -import org.copperengine.core.persistent.ScottyDBStorageInterface; -import org.copperengine.core.persistent.hybrid.HybridDBStorage; - -public class DataCreator { - - public static void main(final String[] args) { - final LoadTestCassandraEngineFactory factory = new LoadTestCassandraEngineFactory() { - @Override - protected ScottyDBStorageInterface createDBStorage() { - return new HybridDBStorage(serializer.get(), workflowRepository.get(), storage.get(), timeoutManager.get(), executorService.get()) { - @Override - public List> dequeue(String ppoolId, int max) throws Exception { - return Collections.emptyList(); - } - }; - } - }; - try { - factory.getEngine().startup(); - createData(factory.getEngine()); - } catch (Exception e) { - e.printStackTrace(); - } finally { - factory.destroyEngine(); - } - } - - private static void createData(PersistentScottyEngine engine) throws Exception { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 4096; i++) { - sb.append(i % 10); - } - final String payload = sb.toString(); - - for (int i = 0; i < 500000; i++) { - final String id = engine.createUUID(); - final LoadTestData data = new LoadTestData(); - data.id = id; - data.someData = payload; - final WorkflowInstanceDescr wfInstanceDescr = new WorkflowInstanceDescr("org.copperengine.core.persistent.cassandra.loadtest.workflows.LoadTestWorkflow", data, id, 1, null); - engine.run(wfInstanceDescr); - } - } -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DummyResponseSender.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DummyResponseSender.java deleted file mode 100644 index ff592e3d..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/DummyResponseSender.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.Acknowledge; -import org.copperengine.core.ProcessingEngine; -import org.copperengine.core.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DummyResponseSender { - - private static final Logger logger = LoggerFactory.getLogger(DummyResponseSender.class); - - private final ScheduledExecutorService exec; - private final ProcessingEngine engine; - - public DummyResponseSender(ScheduledExecutorService exec, ProcessingEngine engine) { - super(); - this.exec = exec; - this.engine = engine; - } - - public void foo(final String cid, final int delay, final TimeUnit timeUnit) { - if (delay == 0) { - engine.notify(new Response(cid, "foo" + cid, null), new Acknowledge.BestEffortAcknowledge()); - } - else { - exec.schedule(new Runnable() { - @Override - public void run() { - logger.debug("notify for cid={}", cid); - engine.notify(new Response(cid, "foo" + cid, null), new Acknowledge.BestEffortAcknowledge()); - } - }, delay, timeUnit); - } - } -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/EngineStarter.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/EngineStarter.java deleted file mode 100644 index c2b1d42a..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/EngineStarter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -public class EngineStarter { - - public static void main(final String[] args) { - final LoadTestCassandraEngineFactory factory = new LoadTestCassandraEngineFactory(); - try { - factory.getEngine().startup(); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - factory.destroyEngine(); - } - }); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestCassandraEngineFactory.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestCassandraEngineFactory.java deleted file mode 100644 index bdd7bf54..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestCassandraEngineFactory.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.util.Arrays; - -import org.copperengine.core.persistent.cassandra.CassandraSessionManager; -import org.copperengine.core.util.Backchannel; -import org.copperengine.core.util.BackchannelDefaultImpl; -import org.copperengine.core.util.PojoDependencyInjector; -import org.copperengine.ext.util.Supplier2Provider; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - -public class LoadTestCassandraEngineFactory extends org.copperengine.core.persistent.cassandra.CassandraEngineFactory { - - public final Supplier backchannel; - public final Supplier dummyResponseSender; - protected final boolean truncate = false; - - public LoadTestCassandraEngineFactory() { - super(Arrays.asList("org.copperengine.core.persistent.cassandra.loadtest.workflows")); - super.setCassandraHosts(Arrays.asList("nuc1.scoop-gmbh.de")); - - backchannel = Suppliers.memoize(new Supplier() { - @Override - public Backchannel get() { - return new BackchannelDefaultImpl(); - } - }); - dummyResponseSender = Suppliers.memoize(new Supplier() { - @Override - public DummyResponseSender get() { - return new DummyResponseSender(scheduledExecutorService.get(), engine.get()); - } - }); - dependencyInjector.get().register("dummyResponseSender", new Supplier2Provider<>(dummyResponseSender)); - dependencyInjector.get().register("backchannel", new Supplier2Provider<>(backchannel)); - } - - @Override - protected CassandraSessionManager createCassandraSessionManager() { - final CassandraSessionManager csm = super.createCassandraSessionManager(); - if (truncate) { - csm.getSession().execute("truncate COP_WORKFLOW_INSTANCE"); - csm.getSession().execute("truncate COP_EARLY_RESPONSE"); - csm.getSession().execute("truncate COP_WFI_ID"); - } - return csm; - } - - @Override - protected PojoDependencyInjector createDependencyInjector() { - return new PojoDependencyInjector(); - } - - public Backchannel getBackchannel() { - return backchannel.get(); - } - -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestData.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestData.java deleted file mode 100644 index 9da03b2b..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/LoadTestData.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.io.Serializable; - -public class LoadTestData implements Serializable { - - private static final long serialVersionUID = 1L; - - public String id; - public String someData; - - public LoadTestData() { - } - - public LoadTestData(String id, String someData) { - this.id = id; - this.someData = someData; - } - -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/PermanentLoadCreator.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/PermanentLoadCreator.java deleted file mode 100644 index e77b4dc6..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/PermanentLoadCreator.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.copperengine.core.WorkflowInstanceDescr; -import org.copperengine.core.persistent.PersistentScottyEngine; - -public class PermanentLoadCreator { - - private static final String WF_CLASS = "org.copperengine.core.persistent.cassandra.loadtest.workflows.LoadTestWorkflow"; - - private LoadTestCassandraEngineFactory factory; - private final AtomicInteger counter = new AtomicInteger(); - private final String payload; - - public PermanentLoadCreator(int payloadSize) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < payloadSize; i++) { - sb.append(i % 10); - } - payload = sb.toString(); - } - - public synchronized PermanentLoadCreator start() throws Exception { - if (factory != null) - return this; - - factory = new LoadTestCassandraEngineFactory(); - factory.getEngine().startup(); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - factory.destroyEngine(); - } - }); - return this; - } - - public PermanentLoadCreator startThread() { - new Thread() { - @Override - public void run() { - for (;;) { - work(); - } - } - }.start(); - return this; - } - - public void work() { - try { - final PersistentScottyEngine engine = factory.getEngine(); - List cids = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - final String cid = engine.createUUID(); - final LoadTestData data = new LoadTestData(cid, payload); - final WorkflowInstanceDescr wfid = new WorkflowInstanceDescr(WF_CLASS, data, cid, 1, null); - engine.run(wfid); - cids.add(cid); - } - for (String cid : cids) { - factory.getBackchannel().wait(cid, 5, TimeUnit.MINUTES); - int value = counter.incrementAndGet(); - if (value % 10000 == 0) { - System.out.println(new Date() + " - " + value + " workflow instances processed so far."); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void main(String[] args) { - try { - new PermanentLoadCreator(4096).start().startThread().startThread().startThread(); - System.out.println("Started!"); - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/TestMain.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/TestMain.java deleted file mode 100644 index f2d62f98..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/TestMain.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra.loadtest; - -import java.util.Arrays; - -import org.copperengine.core.persistent.cassandra.CassandraSessionManagerImpl; - -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.Session; - -public class TestMain { - - public static void main(String[] args) { - int counter = 0; - CassandraSessionManagerImpl sessionManagerImpl = new CassandraSessionManagerImpl(Arrays.asList("nuc1.scoop-gmbh.de", "nuc2.scoop-gmbh.de"), null, "copper_red"); - sessionManagerImpl.startup(); - try { - Session session = sessionManagerImpl.getSession(); - PreparedStatement stmt = session.prepare("SELECT ID FROM COP_WORKFLOW_INSTANCE"); - long startTS = System.currentTimeMillis(); - ResultSet rs = session.execute(stmt.bind().setConsistencyLevel(ConsistencyLevel.TWO).setFetchSize(20)); - Row row = null; - while ((row = rs.one()) != null) { - System.out.println(row.getString("ID")); - counter++; - } - long et = System.currentTimeMillis() - startTS; - System.out.println(et); - } catch (Exception e) { - e.printStackTrace(); - } - sessionManagerImpl.shutdown(); - System.out.println(counter); - } - -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/workflows/LoadTestWorkflow.java b/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/workflows/LoadTestWorkflow.java deleted file mode 100644 index 8b22f150..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/java/org/copperengine/core/persistent/cassandra/loadtest/workflows/LoadTestWorkflow.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.copperengine.core.persistent.cassandra.loadtest.workflows; - -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.AutoWire; -import org.copperengine.core.Interrupt; -import org.copperengine.core.Response; -import org.copperengine.core.WaitMode; -import org.copperengine.core.persistent.PersistentWorkflow; -import org.copperengine.core.persistent.cassandra.loadtest.DummyResponseSender; -import org.copperengine.core.persistent.cassandra.loadtest.LoadTestData; -import org.copperengine.core.util.Backchannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class LoadTestWorkflow extends PersistentWorkflow { - - private static final long serialVersionUID = 1L; - private static final Logger logger = LoggerFactory.getLogger(LoadTestWorkflow.class); - private static final int DEFAULT_TIMEOUT = 5000; - - private transient DummyResponseSender dummyResponseSender; - private transient Backchannel backchannel; - - @AutoWire(beanId = "backchannel") - public void setBackchannel(Backchannel backchannel) { - this.backchannel = backchannel; - } - - @AutoWire(beanId = "dummyResponseSender") - public void setDummyResponseSender(DummyResponseSender dummyResponseSender) { - this.dummyResponseSender = dummyResponseSender; - } - - @Override - public void main() throws Interrupt { - try { - logger.info("started"); - - logger.info("Testing delayed response..."); - delayedResponse(); - - logger.info("Testing early response..."); - earlyResponse(); - - logger.info("Testing timeout response..."); - timeoutResponse(); - - logger.info("Testing delayed multi response..."); - delayedMultiResponse(); - - backchannel.notify(getData().id, "OK"); - logger.info("finished"); - } catch (Exception e) { - logger.error("workflow failed", e); - backchannel.notify(getData().id, e); - System.exit(0); - } catch (AssertionError e) { - logger.error("workflow failed", e); - backchannel.notify(getData().id, e); - System.exit(0); - } - } - - private void delayedResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - dummyResponseSender.foo(cid, 100, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid); - checkResponse(cid); - } - - private void earlyResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - dummyResponseSender.foo(cid, 0, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid); - checkResponse(cid); - } - - private void checkResponse(final String cid) { - Response r = getAndRemoveResponse(cid); - if (r == null) { - logger.warn("Response is null for wfid=" + getId() + " and cid=" + cid); - } - else { - String expectedResponse = "foo" + cid; - if (!expectedResponse.equals(r.getResponse())) { - logger.warn("Unexpected response for wfid=" + getId() + " and cid=" + cid + ": expected=" + expectedResponse + " received=" + r.getResponse()); - } - } - } - - private void timeoutResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - wait(WaitMode.ALL, 100, cid); - Response r = getAndRemoveResponse(cid); - if (r == null) { - logger.warn("Response is null for wfid=" + getId() + " and cid=" + cid); - } - else { - if (!r.isTimeout()) { - logger.warn("Expected timeout for wfid=" + getId() + " and cid=" + cid); - } - } - } - - private void delayedMultiResponse() throws Interrupt { - final String cid1 = getEngine().createUUID(); - final String cid2 = getEngine().createUUID(); - final String cid3 = getEngine().createUUID(); - dummyResponseSender.foo(cid1, 50, TimeUnit.MILLISECONDS); - dummyResponseSender.foo(cid2, 100, TimeUnit.MILLISECONDS); - dummyResponseSender.foo(cid3, 150, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid1, cid2, cid3); - checkResponse(cid1); - checkResponse(cid2); - checkResponse(cid3); - - } - -} diff --git a/projects/copper-cassandra/cassandra-loadtest/src/main/resources/log4j.properties b/projects/copper-cassandra/cassandra-loadtest/src/main/resources/log4j.properties deleted file mode 100644 index b9fb09fb..00000000 --- a/projects/copper-cassandra/cassandra-loadtest/src/main/resources/log4j.properties +++ /dev/null @@ -1,43 +0,0 @@ -# -# Copyright 2002-2015 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Set root logger level to DEBUG and its only appender to A1. -log4j.rootLogger=WARN, A2, A1 - -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender -log4j.appender.A2=org.apache.log4j.FileAppender -log4j.appender.StatisticsAppender=org.apache.log4j.FileAppender - -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n -#log4j.appender.A1.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss,SSS} [%t] %-5p %c{1} - %m%n - -log4j.appender.A2.File=coppper-cassandra-loadtest.log -log4j.appender.A2.layout=org.apache.log4j.PatternLayout -log4j.appender.A2.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n -log4j.appender.A2.append=false - -#log4j.logger.org.copperengine=INFO -log4j.logger.org.copperengine.core.instrument=INFO -log4j.logger.org.copperengine.core.wfrepo=INFO -#log4j.logger.org.copperengine.core.persistent.cassandra=INFO -#log4j.logger.org.copperengine.core.persistent.hybrid=INFO - -log4j.logger.stat=INFO -log4j.logger.org.copperengine.core.persistent.hybrid.StorageCache=INFO - diff --git a/projects/copper-cassandra/cassandra-storage/cassandra-getting-started.txt b/projects/copper-cassandra/cassandra-storage/cassandra-getting-started.txt deleted file mode 100644 index ae8a5f92..00000000 --- a/projects/copper-cassandra/cassandra-storage/cassandra-getting-started.txt +++ /dev/null @@ -1,45 +0,0 @@ -1. -Set up your Apache Cassandra database -see http://wiki.apache.org/cassandra/GettingStarted - -2. -Create a keyspace - -Connect to your Cassandra database, using Cassandra's cqlsh: -> cqlsh - -If you have a single node cluster, create a keyspace as follows: -> CREATE KEYSPACE copper WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; - -If you have a multi node cluster, create an appropriate keyspace. Here is an example for -a keyspace in a single datacenter named DC1 with a replication factor of three: -> CREATE KEYSPACE copper WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'DC1' : 3 }; - -3. -Create the copper schema - -In the cqlsh, switch to keyspace copper: -> use copper; - -Create the schema: -copy the content of file /cassandra-storage/src/main/resources/org/copperengine/core/persistent/cassandra/copper-schema.sql into the cqlsh shell - -Quit the cqlsh -> quit; - -4. -In your Java project - -add copper to your dependencies, e.g. when using gradle like this: - compile 'org.copper-engine:cassandra-storage-4.0.0-alpha1' - -Create and start the copper engine: - - PojoCassandraEngineFactory factory = new PojoCassandraEngineFactory( - Arrays.asList("package.of.your.copper.workflow.classes"), // replace this with the java package(s) containing your copper workflows - Arrays.asList("cassandraHost1", "cassandraHost2") // replace this with the cassandra seed host(s) - "localhost" for a local cassandra installation - ); - factory.getEngine().startup(); - - - diff --git a/projects/copper-cassandra/cassandra-storage/database/create-keyspace-cluster.cql b/projects/copper-cassandra/cassandra-storage/database/create-keyspace-cluster.cql deleted file mode 100644 index 1e5db39d..00000000 --- a/projects/copper-cassandra/cassandra-storage/database/create-keyspace-cluster.cql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE KEYSPACE copper WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'DC1' : 3 }; - diff --git a/projects/copper-cassandra/cassandra-storage/database/create-keyspace-singlenode.cql b/projects/copper-cassandra/cassandra-storage/database/create-keyspace-singlenode.cql deleted file mode 100644 index d247767f..00000000 --- a/projects/copper-cassandra/cassandra-storage/database/create-keyspace-singlenode.cql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE KEYSPACE copper WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; - diff --git a/projects/copper-cassandra/cassandra-storage/database/truncate-all-tables.cql b/projects/copper-cassandra/cassandra-storage/database/truncate-all-tables.cql deleted file mode 100644 index 5611af02..00000000 --- a/projects/copper-cassandra/cassandra-storage/database/truncate-all-tables.cql +++ /dev/null @@ -1,3 +0,0 @@ -truncate cop_early_response; -truncate cop_workflow_instance; -truncate cop_wfi_id; diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/module-info.java b/projects/copper-cassandra/cassandra-storage/src/main/java/module-info.java deleted file mode 100644 index 68d71eb9..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/module-info.java +++ /dev/null @@ -1,17 +0,0 @@ -module org.copperengine.cassandra.storage { - requires transitive org.copperengine.core; - requires transitive org.copperengine.ext; - requires org.copperengine.management; - - requires java.sql; - - requires org.slf4j; - requires commons.lang; - requires transitive cassandra.driver.core; - requires transitive com.google.common; - requires com.fasterxml.jackson.core; - requires com.fasterxml.jackson.databind; - - exports org.copperengine.core.persistent.cassandra; - exports org.copperengine.core.persistent.hybrid; -} \ No newline at end of file diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/AlwaysRetryPolicy.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/AlwaysRetryPolicy.java deleted file mode 100644 index 3a0daf46..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/AlwaysRetryPolicy.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.WriteType; -import com.datastax.driver.core.exceptions.DriverException; -import com.datastax.driver.core.policies.RetryPolicy; - -/** - * A Cassandra {@link RetryPolicy} that will always retry. - * - * @author austermann - * - */ -class AlwaysRetryPolicy implements RetryPolicy { - - @Override - public RetryDecision onReadTimeout(Statement statement, ConsistencyLevel cl, int requiredResponses, int receivedResponses, boolean dataRetrieved, int nbRetry) { - return RetryDecision.retry(cl); - } - - @Override - public RetryDecision onWriteTimeout(Statement statement, ConsistencyLevel cl, WriteType writeType, int requiredAcks, int receivedAcks, int nbRetry) { - return RetryDecision.retry(cl); - } - - @Override - public RetryDecision onUnavailable(Statement statement, ConsistencyLevel cl, int requiredReplica, int aliveReplica, int nbRetry) { - return RetryDecision.retry(cl); - } - - @Override - public RetryDecision onRequestError(Statement statement, ConsistencyLevel cl, DriverException e, int nbRetry) { - return RetryDecision.retry(cl); - } - - @Override - public void init(Cluster cluster) { - - } - - @Override - public void close() { - - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactory.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactory.java deleted file mode 100644 index 0b16e0e0..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactory.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.DependencyInjector; -import org.copperengine.core.persistent.PersistentScottyEngine; -import org.copperengine.core.persistent.hybrid.HybridEngineFactory; -import org.copperengine.core.persistent.hybrid.Storage; -import org.copperengine.core.persistent.hybrid.StorageCache; -import org.slf4j.Logger; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - -/** - * Utility class to create a {@link PersistentScottyEngine} using a cassandra cluster as underlying storage. - *

- * Usage is quite simple, e.g. using a SupplierDependencyInjector: - * - *

- * CassandraEngineFactory<SupplierDependencyInjector> engineFactory = new CassandraEngineFactory<SupplierDependencyInjector>(Arrays.asList("package.of.copper.workflow.classes")) {
- *     @Override
- *     protected SupplierDependencyInjector createDependencyInjector() {
- *         return new SupplierDependencyInjector();
- *     }
- * };
- * engineFactory.getEngine().startup();
- * 
- * - * @author austermann - * - * @param - * type of DependencyInjector to be used from the created engine - */ -public abstract class CassandraEngineFactory extends HybridEngineFactory { - - private static final Logger logger = org.slf4j.LoggerFactory.getLogger(CassandraEngineFactory.class); - - private String keyspace = "copper"; - private List cassandraHosts = Collections.singletonList("localhost"); - private Integer cassandraPort = null; - private boolean withCache = false; - - protected final Supplier cassandraSessionManager; - protected final Supplier scheduledExecutorService; - - public CassandraEngineFactory(List wfPackges) { - super(wfPackges); - - cassandraSessionManager = Suppliers.memoize(new Supplier() { - @Override - public CassandraSessionManager get() { - logger.info("Creating CassandraSessionManager..."); - return createCassandraSessionManager(); - } - }); - - scheduledExecutorService = Suppliers.memoize(new Supplier() { - @Override - public ScheduledExecutorService get() { - logger.info("Creating ScheduledExecutorService..."); - return createScheduledExecutorService(); - } - }); - } - - public void setCassandraHosts(List cassandraHosts) { - this.cassandraHosts = cassandraHosts; - } - - public void setCassandraPort(Integer cassandraPort) { - this.cassandraPort = cassandraPort; - } - - public void setKeyspace(String keyspace) { - this.keyspace = keyspace; - } - - public void setWithCache(boolean withCache) { - this.withCache = withCache; - } - - protected ScheduledExecutorService createScheduledExecutorService() { - return Executors.newScheduledThreadPool(2); - } - - protected Storage createStorage() { - final CassandraStorage cs = new CassandraStorage(cassandraSessionManager.get(), executorService.get(), statisticCollector.get()); - if (withCache) { - final StorageCache storageCache = new StorageCache(cs); - scheduledExecutorService.get().scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - storageCache.logCacheStats(); - } - }, getStatLoggerIntervalSeconds(), getStatLoggerIntervalSeconds(), TimeUnit.SECONDS); - return storageCache; - } - else { - return cs; - } - } - - protected CassandraSessionManager createCassandraSessionManager() { - CassandraSessionManagerImpl x = new CassandraSessionManagerImpl(cassandraHosts, cassandraPort, keyspace); - x.startup(); - return x; - } - - public void destroyEngine() { - super.destroyEngine(); - - cassandraSessionManager.get().shutdown(); - - scheduledExecutorService.get().shutdown(); - } - - public CassandraSessionManager getCassandraSessionManager() { - return cassandraSessionManager.get(); - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraOperation.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraOperation.java deleted file mode 100644 index 923086b0..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraOperation.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import org.slf4j.Logger; - -import com.datastax.driver.core.exceptions.NoHostAvailableException; -import com.datastax.driver.core.exceptions.QueryExecutionException; - -abstract class CassandraOperation { - - private final Logger logger; - - public CassandraOperation(Logger logger) { - this.logger = logger; - } - - public T run() throws Exception { - for (int i = 1;; i++) { - try { - return execute(); - } catch (QueryExecutionException | NoHostAvailableException e) { - logger.warn("Cassandra operation failed - retrying...", e); - } catch (Exception e) { - throw e; - } - final int sleepIntervalMSec = calculateSleepInterval(i); - logger.debug("Going to sleep {} msec before next try", sleepIntervalMSec); - Thread.sleep(sleepIntervalMSec); - } - } - - protected abstract T execute() throws Exception; - - protected int calculateSleepInterval(int c) { - return Math.min(5000, 50 * c); - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManager.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManager.java deleted file mode 100644 index 0439f5ae..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManager.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Session; - -public interface CassandraSessionManager { - public Session getSession(); - - public Cluster getCluster(); - - public void startup(); - - public void shutdown(); -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImpl.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImpl.java deleted file mode 100644 index f95d2594..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.Collection; - -import org.apache.commons.lang.NullArgumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Cluster.Builder; -import com.datastax.driver.core.Host; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy; -import com.datastax.driver.core.policies.TokenAwarePolicy; - -public class CassandraSessionManagerImpl implements CassandraSessionManager { - - private static final Logger logger = LoggerFactory.getLogger(CassandraSessionManagerImpl.class); - - private final String keyspace; - private final Collection hosts; - private final Integer port; - private Cluster cassandraCluster; - private Session session; - - public CassandraSessionManagerImpl(Collection hosts, Integer port, String keyspace) { - if (hosts == null || hosts.isEmpty()) - throw new NullArgumentException("hosts"); - if (keyspace == null || keyspace.isEmpty()) - throw new NullArgumentException("keyspace"); - this.hosts = hosts; - this.port = port; - this.keyspace = keyspace; - } - - @Override - public synchronized void startup() { - if (cassandraCluster != null) - return; - - Builder b = Cluster.builder(); - b.withLoadBalancingPolicy(new TokenAwarePolicy(DCAwareRoundRobinPolicy.builder().build())); - for (String host : hosts) { - b.addContactPoint(host); - } - if (port != null) { - b.withPort(port); - } - cassandraCluster = b.build(); - cassandraCluster.getConfiguration().getSocketOptions().setReadTimeoutMillis(60000); - - logger.info("Connected to cluster: {}", cassandraCluster.getMetadata().getClusterName()); - for (Host host : cassandraCluster.getMetadata().getAllHosts()) { - logger.info("Datatacenter: {} Host: {} Rack: {}", host.getDatacenter(), host.getAddress(), host.getRack()); - } - - session = cassandraCluster.connect(keyspace); - } - - @Override - public synchronized void shutdown() { - cassandraCluster.close(); - } - - @Override - public Session getSession() { - return session; - } - - @Override - public Cluster getCluster() { - return cassandraCluster; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerPojo.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerPojo.java deleted file mode 100644 index b6ff7922..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerPojo.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import org.apache.commons.lang.NullArgumentException; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Session; - -public class CassandraSessionManagerPojo implements CassandraSessionManager { - - private final Session session; - private final Cluster cluster; - - public CassandraSessionManagerPojo(final Session session, final Cluster cluster) { - if (session == null) - throw new NullArgumentException("session"); - if (cluster == null) - throw new NullArgumentException("cluster"); - this.session = session; - this.cluster = cluster; - } - - @Override - public Session getSession() { - return session; - } - - @Override - public void startup() { - - } - - @Override - public void shutdown() { - - } - - @Override - public Cluster getCluster() { - return cluster; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraStorage.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraStorage.java deleted file mode 100644 index 5cfed64a..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/CassandraStorage.java +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.lang.NullArgumentException; -import org.copperengine.core.CopperRuntimeException; -import org.copperengine.core.ProcessingState; -import org.copperengine.core.WaitMode; -import org.copperengine.core.monitoring.RuntimeStatisticsCollector; -import org.copperengine.core.persistent.SerializedWorkflow; -import org.copperengine.core.persistent.hybrid.HybridDBStorageAccessor; -import org.copperengine.core.persistent.hybrid.Storage; -import org.copperengine.core.persistent.hybrid.WorkflowInstance; -import org.copperengine.management.model.WorkflowInstanceFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.KeyspaceMetadata; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.policies.DefaultRetryPolicy; -import com.datastax.driver.core.policies.LoggingRetryPolicy; -import com.datastax.driver.core.policies.RetryPolicy; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; - -/** - * Implementation of the {@link Storage} interface backed by a Apache Cassandra DB. - * - * @author austermann - * - */ -public class CassandraStorage implements Storage { - - private static final Logger logger = LoggerFactory.getLogger(CassandraStorage.class); - - private static final String CQL_UPD_WORKFLOW_INSTANCE_NOT_WAITING = "UPDATE COP_WORKFLOW_INSTANCE SET PPOOL_ID=?, PRIO=?, CREATION_TS=?, DATA=?, OBJECT_STATE=?, STATE=?, LAST_MOD_TS=toTimestamp(now()), CLASSNAME=? WHERE ID=?"; - private static final String CQL_UPD_WORKFLOW_INSTANCE_WAITING = "UPDATE COP_WORKFLOW_INSTANCE SET PPOOL_ID=?, PRIO=?, CREATION_TS=?, DATA=?, OBJECT_STATE=?, WAIT_MODE=?, TIMEOUT=?, RESPONSE_MAP_JSON=?, STATE=?, LAST_MOD_TS=toTimestamp(now()), CLASSNAME=? WHERE ID=?"; - private static final String CQL_UPD_WORKFLOW_INSTANCE_STATE = "UPDATE COP_WORKFLOW_INSTANCE SET STATE=?, LAST_MOD_TS=toTimestamp(now()) WHERE ID=?"; - private static final String CQL_UPD_WORKFLOW_INSTANCE_STATE_AND_RESPONSE_MAP = "UPDATE COP_WORKFLOW_INSTANCE SET STATE=?, RESPONSE_MAP_JSON=?, LAST_MOD_TS=toTimestamp(now()) WHERE ID=?"; - private static final String CQL_DEL_WORKFLOW_INSTANCE_WAITING = "DELETE FROM COP_WORKFLOW_INSTANCE WHERE ID=?"; - private static final String CQL_SEL_WORKFLOW_INSTANCE = "SELECT * FROM COP_WORKFLOW_INSTANCE WHERE ID=?"; - private static final String CQL_INS_EARLY_RESPONSE = "INSERT INTO COP_EARLY_RESPONSE (CORRELATION_ID, RESPONSE) VALUES (?,?) USING TTL ?"; - private static final String CQL_DEL_EARLY_RESPONSE = "DELETE FROM COP_EARLY_RESPONSE WHERE CORRELATION_ID=?"; - private static final String CQL_SEL_EARLY_RESPONSE = "SELECT RESPONSE FROM COP_EARLY_RESPONSE WHERE CORRELATION_ID=?"; - private static final String CQL_INS_WFI_ID = "INSERT INTO COP_WFI_ID (ID) VALUES (?)"; - private static final String CQL_DEL_WFI_ID = "DELETE FROM COP_WFI_ID WHERE ID=?"; - private static final String CQL_SEL_WFI_ID_ALL = "SELECT * FROM COP_WFI_ID"; - - private final Executor executor; - private final Session session; - private final Cluster cluster; - private final Map preparedStatements = new HashMap<>(); - private final JsonMapper jsonMapper = new JsonMapperImpl(); - private final ConsistencyLevel consistencyLevel; - private final RuntimeStatisticsCollector runtimeStatisticsCollector; - private final RetryPolicy alwaysRetry = new LoggingRetryPolicy(new AlwaysRetryPolicy()); - private int ttlEarlyResponseSeconds = 1 * 24 * 60 * 60; // one day - private int initializationTimeoutSeconds = 1 * 24 * 60 * 60; // one day - private boolean createSchemaOnStartup = true; - - public CassandraStorage(final CassandraSessionManager sessionManager, final Executor executor, final RuntimeStatisticsCollector runtimeStatisticsCollector) { - this(sessionManager, executor, runtimeStatisticsCollector, ConsistencyLevel.LOCAL_QUORUM); - - } - - public CassandraStorage(final CassandraSessionManager sessionManager, final Executor executor, final RuntimeStatisticsCollector runtimeStatisticsCollector, final ConsistencyLevel consistencyLevel) { - if (sessionManager == null) - throw new NullArgumentException("sessionManager"); - - if (consistencyLevel == null) - throw new NullArgumentException("consistencyLevel"); - - if (executor == null) - throw new NullArgumentException("executor"); - - if (runtimeStatisticsCollector == null) - throw new NullArgumentException("runtimeStatisticsCollector"); - - this.executor = executor; - this.consistencyLevel = consistencyLevel; - this.session = sessionManager.getSession(); - this.cluster = sessionManager.getCluster(); - this.runtimeStatisticsCollector = runtimeStatisticsCollector; - - } - - public void setCreateSchemaOnStartup(boolean createSchemaOnStartup) { - this.createSchemaOnStartup = createSchemaOnStartup; - } - - protected void prepareStatements() throws Exception { - prepare(CQL_UPD_WORKFLOW_INSTANCE_NOT_WAITING); - prepare(CQL_UPD_WORKFLOW_INSTANCE_WAITING); - prepare(CQL_DEL_WORKFLOW_INSTANCE_WAITING); - prepare(CQL_SEL_WORKFLOW_INSTANCE); - prepare(CQL_UPD_WORKFLOW_INSTANCE_STATE); - prepare(CQL_INS_EARLY_RESPONSE); - prepare(CQL_DEL_EARLY_RESPONSE); - prepare(CQL_SEL_EARLY_RESPONSE); - prepare(CQL_UPD_WORKFLOW_INSTANCE_STATE_AND_RESPONSE_MAP); - prepare(CQL_INS_WFI_ID); - prepare(CQL_DEL_WFI_ID); - prepare(CQL_SEL_WFI_ID_ALL, DefaultRetryPolicy.INSTANCE); - } - - protected void createSchema(Session session, Cluster cluster) throws Exception { - if (!createSchemaOnStartup) - return; - - final KeyspaceMetadata metaData = cluster.getMetadata().getKeyspace(session.getLoggedKeyspace()); - if (metaData.getTable("COP_WORKFLOW_INSTANCE") != null) { - logger.info("skipping schema creation"); - return; - } - - logger.info("Creating tables..."); - try (final BufferedReader br = new BufferedReader(new InputStreamReader(CassandraStorage.class.getResourceAsStream("copper-schema.cql")))) { - StringBuilder cql = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) - continue; - if (line.startsWith("--")) - continue; - if (line.endsWith(";")) { - if (line.length() > 1) - cql.append(line.substring(0, line.length() - 1)); - String cqlCmd = cql.toString(); - cql = new StringBuilder(); - logger.info("Executing CQL {}", cqlCmd); - session.execute(cqlCmd); - } - else { - cql.append(line).append(" "); - } - } - } - - } - - public void setTtlEarlyResponseSeconds(int ttlEarlyResponseSeconds) { - if (ttlEarlyResponseSeconds <= 0) - throw new IllegalArgumentException(); - this.ttlEarlyResponseSeconds = ttlEarlyResponseSeconds; - } - - public void setInitializationTimeoutSeconds(int initializationTimeoutSeconds) { - if (initializationTimeoutSeconds <= 0) - throw new IllegalArgumentException(); - this.initializationTimeoutSeconds = initializationTimeoutSeconds; - } - - @Override - public void safeWorkflowInstance(final WorkflowInstance cw, final boolean initialInsert) throws Exception { - logger.debug("safeWorkflow({})", cw); - new CassandraOperation(logger) { - @Override - protected Void execute() throws Exception { - if (initialInsert) { - final PreparedStatement pstmt = preparedStatements.get(CQL_INS_WFI_ID); - final long startTS = System.nanoTime(); - session.execute(pstmt.bind(cw.id)); - runtimeStatisticsCollector.submit("wfii.ins", 1, System.nanoTime() - startTS, TimeUnit.NANOSECONDS); - } - if (cw.cid2ResponseMap == null || cw.cid2ResponseMap.isEmpty()) { - final PreparedStatement pstmt = preparedStatements.get(CQL_UPD_WORKFLOW_INSTANCE_NOT_WAITING); - final long startTS = System.nanoTime(); - session.execute(pstmt.bind(cw.ppoolId, cw.prio, cw.creationTS, cw.serializedWorkflow.getData(), cw.serializedWorkflow.getObjectState(), cw.state.name(), cw.classname, cw.id)); - runtimeStatisticsCollector.submit("wfi.update.nowait", 1, System.nanoTime() - startTS, TimeUnit.NANOSECONDS); - } - else { - final PreparedStatement pstmt = preparedStatements.get(CQL_UPD_WORKFLOW_INSTANCE_WAITING); - final String responseMapJson = jsonMapper.toJSON(cw.cid2ResponseMap); - final long startTS = System.nanoTime(); - session.execute(pstmt.bind(cw.ppoolId, cw.prio, cw.creationTS, cw.serializedWorkflow.getData(), cw.serializedWorkflow.getObjectState(), cw.waitMode.name(), cw.timeout, responseMapJson, cw.state.name(), cw.classname, cw.id)); - runtimeStatisticsCollector.submit("wfi.update.wait", 1, System.nanoTime() - startTS, TimeUnit.NANOSECONDS); - } - return null; - } - }.run(); - } - - @Override - public ListenableFuture deleteWorkflowInstance(String wfId) throws Exception { - logger.debug("deleteWorkflowInstance({})", wfId); - session.executeAsync(preparedStatements.get(CQL_DEL_WFI_ID).bind(wfId)); - final PreparedStatement pstmt = preparedStatements.get(CQL_DEL_WORKFLOW_INSTANCE_WAITING); - final long startTS = System.nanoTime(); - final ResultSetFuture rsf = session.executeAsync(pstmt.bind(wfId)); - return createSettableFuture(rsf, "wfi.delete", startTS); - } - - private SettableFuture createSettableFuture(final ResultSetFuture rsf, final String mpId, final long startTsNanos) { - final SettableFuture rv = SettableFuture.create(); - rsf.addListener(new Runnable() { - @Override - public void run() { - try { - runtimeStatisticsCollector.submit(mpId, 1, System.nanoTime() - startTsNanos, TimeUnit.NANOSECONDS); - rsf.get(); - rv.set(null); - } catch (InterruptedException e) { - rv.setException(e); - } catch (ExecutionException e) { - rv.setException(e.getCause()); - } - - } - }, executor); - return rv; - } - - @Override - public WorkflowInstance readWorkflowInstance(final String wfId) throws Exception { - logger.debug("readCassandraWorkflow({})", wfId); - return new CassandraOperation(logger) { - @Override - protected WorkflowInstance execute() throws Exception { - final PreparedStatement pstmt = preparedStatements.get(CQL_SEL_WORKFLOW_INSTANCE); - final long startTS = System.nanoTime(); - ResultSet rs = session.execute(pstmt.bind(wfId)); - Row row = rs.one(); - if (row == null) { - return null; - } - final WorkflowInstance cw = row2WorkflowInstance(row); - runtimeStatisticsCollector.submit("wfi.read", 1, System.nanoTime() - startTS, TimeUnit.NANOSECONDS); - return cw; - } - }.run(); - } - - @Override - public ListenableFuture safeEarlyResponse(String correlationId, String serializedResponse) throws Exception { - logger.debug("safeEarlyResponse({})", correlationId); - final long startTS = System.nanoTime(); - final ResultSetFuture rsf = session.executeAsync(preparedStatements.get(CQL_INS_EARLY_RESPONSE).bind(correlationId, serializedResponse, ttlEarlyResponseSeconds)); - return createSettableFuture(rsf, "ear.insert", startTS); - } - - @Override - public String readEarlyResponse(final String correlationId) throws Exception { - logger.debug("readEarlyResponse({})", correlationId); - return new CassandraOperation(logger) { - @Override - protected String execute() throws Exception { - final long startTS = System.nanoTime(); - final ResultSet rs = session.execute(preparedStatements.get(CQL_SEL_EARLY_RESPONSE).bind(correlationId)); - Row row = rs.one(); - runtimeStatisticsCollector.submit("ear.read", 1, System.nanoTime() - startTS, TimeUnit.NANOSECONDS); - if (row != null) { - logger.debug("early response with correlationId {} found!", correlationId); - return row.getString("RESPONSE"); - } - return null; - } - }.run(); - } - - @Override - public ListenableFuture deleteEarlyResponse(String correlationId) throws Exception { - logger.debug("deleteEarlyResponse({})", correlationId); - final long startTS = System.nanoTime(); - final ResultSetFuture rsf = session.executeAsync(preparedStatements.get(CQL_DEL_EARLY_RESPONSE).bind(correlationId)); - return createSettableFuture(rsf, "ear.delete", startTS); - } - - @Override - public void initialize(final HybridDBStorageAccessor internalStorageAccessor, int numberOfThreads) throws Exception { - createSchema(session, cluster); - - prepareStatements(); - - // TODO instead of blocking the startup until all active workflow instances are read and resumed, it is - // sufficient to read just their existing IDs in COP_WFI_ID and resume them in the background while already - // starting the engine an accepting new instances. - - if (numberOfThreads <= 0) - numberOfThreads = 1; - logger.info("Starting to initialize with {} threads ...", numberOfThreads); - final ExecutorService execService = Executors.newFixedThreadPool(numberOfThreads); - final long startTS = System.currentTimeMillis(); - final ResultSet rs = session.execute(preparedStatements.get(CQL_SEL_WFI_ID_ALL).bind().setFetchSize(500).setConsistencyLevel(ConsistencyLevel.ONE)); - int counter = 0; - Row row; - while ((row = rs.one()) != null) { - counter++; - final String wfId = row.getString("ID"); - execService.execute(new Runnable() { - @Override - public void run() { - try { - resume(wfId, internalStorageAccessor); - } - catch (Exception e) { - logger.error("resume failed", e); - } - } - }); - } - logger.info("Read {} IDs in {} msec", counter, System.currentTimeMillis() - startTS); - execService.shutdown(); - final boolean timeoutHappened = !execService.awaitTermination(initializationTimeoutSeconds, TimeUnit.SECONDS); - if (timeoutHappened) { - throw new CopperRuntimeException("initialize timed out!"); - } - logger.info("Finished initialization - read {} rows in {} msec", counter, System.currentTimeMillis() - startTS); - runtimeStatisticsCollector.submit("storage.init", counter, System.currentTimeMillis() - startTS, TimeUnit.MILLISECONDS); - } - - private void resume(final String wfId, final HybridDBStorageAccessor internalStorageAccessor) throws Exception { - logger.trace("resume(wfId={})", wfId); - - final ResultSet rs = session.execute(preparedStatements.get(CQL_SEL_WORKFLOW_INSTANCE).bind(wfId)); - final Row row = rs.one(); - if (row == null) { - logger.warn("No workflow instance {} found - deleting row in COP_WFI_ID", wfId); - session.executeAsync(preparedStatements.get(CQL_DEL_WFI_ID).bind(wfId)); - return; - } - - final String ppoolId = row.getString("PPOOL_ID"); - final int prio = row.getInt("PRIO"); - final WaitMode waitMode = toWaitMode(row.getString("WAIT_MODE")); - final Map responseMap = toResponseMap(row.getString("RESPONSE_MAP_JSON")); - final ProcessingState state = ProcessingState.valueOf(row.getString("STATE")); - final Date timeout = row.getTimestamp("TIMEOUT"); - final boolean timeoutOccured = timeout != null && timeout.getTime() <= System.currentTimeMillis(); - - if (state == ProcessingState.ERROR || state == ProcessingState.INVALID) { - return; - } - - if (state == ProcessingState.ENQUEUED) { - internalStorageAccessor.enqueue(wfId, ppoolId, prio); - return; - } - - if (responseMap != null) { - final List missingResponseCorrelationIds = new ArrayList(); - int numberOfAvailableResponses = 0; - for (Entry e : responseMap.entrySet()) { - final String correlationId = e.getKey(); - final String response = e.getValue(); - internalStorageAccessor.registerCorrelationId(correlationId, wfId); - if (response != null) { - numberOfAvailableResponses++; - } - else { - missingResponseCorrelationIds.add(correlationId); - } - } - boolean modified = false; - if (!missingResponseCorrelationIds.isEmpty()) { - // check for early responses - for (String cid : missingResponseCorrelationIds) { - String earlyResponse = readEarlyResponse(cid); - if (earlyResponse != null) { - responseMap.put(cid, earlyResponse); - numberOfAvailableResponses++; - modified = true; - } - } - } - if (modified || timeoutOccured) { - final ProcessingState newState = (timeoutOccured || numberOfAvailableResponses == responseMap.size() || (numberOfAvailableResponses == 1 && waitMode == WaitMode.FIRST)) ? ProcessingState.ENQUEUED : ProcessingState.WAITING; - final String responseMapJson = jsonMapper.toJSON(responseMap); - session.execute(preparedStatements.get(CQL_UPD_WORKFLOW_INSTANCE_STATE_AND_RESPONSE_MAP).bind(newState.name(), responseMapJson, wfId)); - if (newState == ProcessingState.ENQUEUED) { - internalStorageAccessor.enqueue(wfId, ppoolId, prio); - } - } - - } - } - - @Override - public ListenableFuture updateWorkflowInstanceState(final String wfId, final ProcessingState state) throws Exception { - logger.debug("updateWorkflowInstanceState({}, {})", wfId, state); - final long startTS = System.nanoTime(); - final ResultSetFuture rsf = session.executeAsync(preparedStatements.get(CQL_UPD_WORKFLOW_INSTANCE_STATE).bind(state.name(), wfId)); - return createSettableFuture(rsf, "wfi.update.state", startTS); - } - - @SuppressWarnings("unchecked") - private Map toResponseMap(String v) { - return v == null ? null : jsonMapper.fromJSON(v, HashMap.class); - } - - private WaitMode toWaitMode(String v) { - return v == null ? null : WaitMode.valueOf(v); - } - - private void prepare(String cql) { - prepare(cql, alwaysRetry); - } - - private void prepare(String cql, RetryPolicy petryPolicy) { - logger.info("Preparing cql stmt {}", cql); - PreparedStatement pstmt = session.prepare(cql); - pstmt.setConsistencyLevel(consistencyLevel); - pstmt.setRetryPolicy(petryPolicy); - pstmt.setIdempotent(true); - preparedStatements.put(cql, pstmt); - } - - private void appendQueryBase(StringBuilder query, List values, WorkflowInstanceFilter filter){ - boolean first = true; - if (filter.getWorkflowClassname() != null) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("CLASSNAME=?"); - values.add(filter.getWorkflowClassname()); - } - if (filter.getProcessorPoolId() != null) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("PPOOL_ID=?"); - values.add(filter.getProcessorPoolId()); - } - if (filter.getStates() != null && !filter.getStates().isEmpty()) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("STATE IN (" + String.join(", ", Collections.nCopies( filter.getStates().size(), "?")) + ")"); - values.addAll(filter.getStates()); - } - if (filter.getCreationTS() != null && filter.getCreationTS().getFrom() != null) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("CREATION_TS>=?"); - values.add(filter.getCreationTS().getFrom()); - } - if (filter.getCreationTS() != null && filter.getCreationTS().getTo() != null) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("CREATION_TS=?"); - values.add(filter.getLastModTS().getFrom()); - } - if (filter.getLastModTS() != null && filter.getLastModTS().getTo() != null) { - query.append(first ? " WHERE " : " AND "); - first=false; - query.append("LAST_MOD_TS queryWorkflowInstances(WorkflowInstanceFilter filter) throws Exception { - final StringBuilder query = new StringBuilder(); - final List values = new ArrayList<>(); - query.append("SELECT * FROM COP_WORKFLOW_INSTANCE"); - appendQueryBase(query, values, filter); - query.append(" LIMIT ").append(filter.getMax()); - query.append(" ALLOW FILTERING"); - final String cqlQuery = query.toString(); - logger.info("queryWorkflowInstances - cqlQuery = {}", cqlQuery); - final ResultSet resultSet = session.execute(cqlQuery, values.toArray()); - Row row; - final List resultList = new ArrayList<>(); - while ((row = resultSet.one()) != null) { - final WorkflowInstance cw = row2WorkflowInstance(row); - resultList.add(cw); - } - return resultList; - } - - // Probably it's gonna be slow. We can consider creating counting table for that sake. - @Override - public int countWorkflowInstances(WorkflowInstanceFilter filter) throws Exception { - final StringBuilder query = new StringBuilder(); - final List values = new ArrayList<>(); - query.append("SELECT COUNT(*) AS COUNT_NUMBER FROM COP_WORKFLOW_INSTANCE"); - appendQueryBase(query, values, filter); - query.append(" ALLOW FILTERING"); - final String cqlQuery = query.toString(); - logger.info("queryWorkflowInstances - cqlQuery = {}", cqlQuery); - final ResultSet resultSet = session.execute(cqlQuery, values.toArray()); - Row row; - while ((row = resultSet.one()) != null) { - return row.getInt("COUNT_NUMBER"); - } - throw new SQLException("Failed to get result of CQL request for counting workflow instances"); - } - - private WorkflowInstance row2WorkflowInstance(Row row) { - final WorkflowInstance cw = new WorkflowInstance(); - cw.id = row.getString("ID"); - cw.ppoolId = row.getString("PPOOL_ID"); - cw.prio = row.getInt("PRIO"); - cw.creationTS = row.getTimestamp("CREATION_TS"); - cw.timeout = row.getTimestamp("TIMEOUT"); - cw.waitMode = toWaitMode(row.getString("WAIT_MODE")); - cw.serializedWorkflow = new SerializedWorkflow(); - cw.serializedWorkflow.setData(row.getString("DATA")); - cw.serializedWorkflow.setObjectState(row.getString("OBJECT_STATE")); - cw.cid2ResponseMap = toResponseMap(row.getString("RESPONSE_MAP_JSON")); - cw.state = ProcessingState.valueOf(row.getString("STATE")); - cw.lastModTS = row.getTimestamp("LAST_MOD_TS"); - cw.classname = row.getString("CLASSNAME"); - return cw; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapper.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapper.java deleted file mode 100644 index 0aa27e88..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -public interface JsonMapper { - - String toJSON(Object x); - - T fromJSON(String s, Class c); - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapperImpl.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapperImpl.java deleted file mode 100644 index 932908bc..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/JsonMapperImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class JsonMapperImpl implements JsonMapper { - - private final ObjectMapper mapper = new ObjectMapper(); - - @Override - public String toJSON(Object x) { - try { - return mapper.writeValueAsString(x); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - @Override - public T fromJSON(String s, Class c) { - try { - - return mapper.readValue(s, c); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/PojoCassandraEngineFactory.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/PojoCassandraEngineFactory.java deleted file mode 100644 index d127ebe3..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/cassandra/PojoCassandraEngineFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.List; - -import org.copperengine.core.util.PojoDependencyInjector; - -public class PojoCassandraEngineFactory extends CassandraEngineFactory { - - public PojoCassandraEngineFactory(final List wfPackges, final List cassandraHosts) { - super(wfPackges); - setCassandraHosts(cassandraHosts); - } - - @Override - protected PojoDependencyInjector createDependencyInjector() { - return new PojoDependencyInjector(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CacheStats.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CacheStats.java deleted file mode 100644 index 5101834a..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CacheStats.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.copperengine.core.persistent.hybrid; - -import java.util.concurrent.atomic.AtomicLong; - -public class CacheStats { - - private AtomicLong numberOfReads = new AtomicLong(); - private AtomicLong numberOfCacheHits = new AtomicLong(); - private AtomicLong numberOfCacheMisses = new AtomicLong(); - - public void incNumberOfReads(boolean hit) { - numberOfReads.incrementAndGet(); - if (hit) - numberOfCacheHits.incrementAndGet(); - else - numberOfCacheMisses.incrementAndGet(); - } - - public long getNumberOfCacheHits() { - return numberOfCacheHits.get(); - } - - public long getNumberOfCacheMisses() { - return numberOfCacheMisses.get(); - } - - public long getNumberOfReads() { - return numberOfReads.get(); - } - - @Override - public String toString() { - return "CacheStats [numberOfReads=" + numberOfReads + ", numberOfCacheHits=" + numberOfCacheHits + ", numberOfCacheMisses=" + numberOfCacheMisses + "]"; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CorrelationIdMap.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CorrelationIdMap.java deleted file mode 100644 index 276e34b8..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/CorrelationIdMap.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -class CorrelationIdMap { - - private final Object mutex = new Object(); - private final Map correlationId2wfIdMap = new HashMap<>(); - private final Map> wfId2correlationIdMap = new HashMap<>(); - - public String getWorkflowId(String correlationId) { - synchronized (mutex) { - return correlationId2wfIdMap.get(correlationId); - } - } - - public boolean containsWorkflowId(String workflowId) { - synchronized (mutex) { - return wfId2correlationIdMap.containsKey(workflowId); - } - } - - public void removeAll4Workflow(String workflowId) { - synchronized (mutex) { - List list = wfId2correlationIdMap.remove(workflowId); - if (list == null || list.isEmpty()) - return; - for (String cid : list) { - correlationId2wfIdMap.remove(cid); - } - } - } - - public void addCorrelationId(String workflowId, String correlationId) { - synchronized (mutex) { - List list = wfId2correlationIdMap.remove(workflowId); - if (list == null) { - list = new ArrayList(); - wfId2correlationIdMap.put(workflowId, list); - } - list.add(correlationId); - correlationId2wfIdMap.put(correlationId, workflowId); - } - } - - public void addCorrelationIds(String workflowId, List correlationIds) { - synchronized (mutex) { - List list = wfId2correlationIdMap.remove(workflowId); - if (list == null) { - list = new ArrayList(correlationIds.size()); - wfId2correlationIdMap.put(workflowId, list); - } - list.addAll(correlationIds); - for (String cid : correlationIds) { - correlationId2wfIdMap.put(cid, workflowId); - } - } - } - - public void addCorrelationIds(String workflowId, String[] correlationIds) { - synchronized (mutex) { - List list = wfId2correlationIdMap.remove(workflowId); - if (list == null) { - list = new ArrayList(correlationIds.length); - wfId2correlationIdMap.put(workflowId, list); - } - for (String cid : correlationIds) { - list.add(cid); - correlationId2wfIdMap.put(cid, workflowId); - } - } - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/DefaultTimeoutManager.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/DefaultTimeoutManager.java deleted file mode 100644 index 9fc7b6b0..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/DefaultTimeoutManager.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Default implementation of the {@link TimeoutManager} interface. - * - * @author austermann - */ -public final class DefaultTimeoutManager extends Thread implements TimeoutManager { - - private final static Logger logger = LoggerFactory.getLogger(TimeoutManager.class); - private final static long SLOT_INTERVAL = 25; - - private final Map slots = new TreeMap(); - private long nextWakeupTime; - private boolean shutdown = false; - private volatile boolean started = false; - - public DefaultTimeoutManager() { - super("copper.Timeoutmanager"); - } - - static long processSlot(Date timeoutTS) { - return processSlot(timeoutTS.getTime()); - } - - static long processSlot(long timeoutTS) { - return ((timeoutTS / SLOT_INTERVAL) + 1) * SLOT_INTERVAL; - } - - public synchronized TimeoutManager startup() { - this.setDaemon(true); - this.start(); - started = true; - return this; - } - - public synchronized TimeoutManager shutdown() { - if (shutdown) - return this; - shutdown = true; - synchronized (slots) { - slots.notify(); - } - return this; - } - - public void run() { - logger.info("started"); - while (!shutdown) { - try { - List expired = new ArrayList(32); - synchronized (slots) { - if (shutdown) - break; - - if (logger.isDebugEnabled()) - logger.debug("Activated at: " + System.currentTimeMillis()); - for (Iterator> i = slots.entrySet().iterator(); i.hasNext();) { - Map.Entry entry = i.next(); - long timeoutTime = entry.getKey(); - if (timeoutTime <= System.currentTimeMillis()) { - i.remove(); - if (logger.isDebugEnabled()) - logger.debug("Expired slot found at: " + timeoutTime); - expired.addAll(entry.getValue().getWfId2RunnableMap().values()); - } else { - break; - } - } - } - for (Runnable r : expired) { - try { - r.run(); - } catch (Exception e) { - logger.error("run failed", e); - } - } - - synchronized (slots) { - if (shutdown) - break; - - Iterator> i = slots.entrySet().iterator(); - if (!i.hasNext()) { - logger.debug("There are currently no timeout slots - waiting indefinitely..."); - nextWakeupTime = 0; - slots.wait(); - } else { - nextWakeupTime = i.next().getValue().getTimeoutTS(); - long delay = nextWakeupTime - System.currentTimeMillis(); - if (delay > 0) { - logger.debug("Sleeping for: " + delay + "msec."); - slots.wait(delay); - } - } - } - } catch (Exception e) { - logger.error("Unexpected exception:", e); - } - } - logger.info("stopped"); - } - - @Override - public void registerTimeout(final Date _timeoutTS, final String workflowId, final Runnable onTimeout) { - logger.debug("registerTimeout({}, {})", _timeoutTS, workflowId); - - if (_timeoutTS == null) - return; - - if (!started) - throw new IllegalStateException("timeout manager not yet started!"); - - final Long timeoutTS = Long.valueOf(processSlot(_timeoutTS)); - if (logger.isDebugEnabled()) { - long currentTime = System.currentTimeMillis(); - logger.debug("currentTime=" + currentTime); - logger.debug("timeoutTS=" + timeoutTS); - logger.debug("nextWakeupTime=" + nextWakeupTime); - } - synchronized (slots) { - TimeoutSlot timeoutSlot = (TimeoutSlot) slots.get(timeoutTS); - if (timeoutSlot == null) { - timeoutSlot = new TimeoutSlot(timeoutTS.longValue()); - slots.put(timeoutTS, timeoutSlot); - if (nextWakeupTime > timeoutTS.longValue() || nextWakeupTime == 0L) - slots.notify(); - } - timeoutSlot.getWfId2RunnableMap().put(workflowId, onTimeout); - } - } - - @Override - public void unregisterTimeout(final Date _timeoutTS, final String workflowId) { - logger.debug("unregisterTimeout({}, {})", _timeoutTS, workflowId); - - if (_timeoutTS == null) - return; - - if (!started) - throw new IllegalStateException("timeout manager not yet started!"); - - final Long timeoutTS = Long.valueOf(processSlot(_timeoutTS)); - synchronized (slots) { - TimeoutSlot timeoutSlot = (TimeoutSlot) slots.get(timeoutTS); - if (timeoutSlot != null) { - timeoutSlot.getWfId2RunnableMap().remove(workflowId); - if (timeoutSlot.getWfId2RunnableMap().isEmpty()) { - slots.remove(timeoutTS); - } - } - } - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorage.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorage.java deleted file mode 100644 index cd85a0b9..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorage.java +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.sql.Connection; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; - -import org.copperengine.core.Acknowledge; -import org.copperengine.core.CopperRuntimeException; -import org.copperengine.core.DuplicateIdException; -import org.copperengine.core.ProcessingState; -import org.copperengine.core.Response; -import org.copperengine.core.WaitMode; -import org.copperengine.core.Workflow; -import org.copperengine.core.common.WorkflowRepository; -import org.copperengine.core.internal.WorkflowAccessor; -import org.copperengine.core.persistent.RegisterCall; -import org.copperengine.core.persistent.ScottyDBStorageInterface; -import org.copperengine.core.persistent.Serializer; -import org.copperengine.core.util.Blocker; -import org.copperengine.management.model.AuditTrailInfo; -import org.copperengine.management.model.AuditTrailInstanceFilter; -import org.copperengine.management.model.WorkflowInstanceFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.util.concurrent.ListenableFuture; - -public class HybridDBStorage implements ScottyDBStorageInterface { - - private static final Logger logger = LoggerFactory.getLogger(HybridDBStorage.class); - private static final Acknowledge.BestEffortAcknowledge ACK = new Acknowledge.BestEffortAcknowledge(); - - private final Executor executor; - private final TimeoutManager timeoutManager; - private final Blocker startupBlocker = new Blocker(true); - private final Map> ppoolId2queueMap; - private final CorrelationIdMap correlationIdMap = new CorrelationIdMap(); - private final Serializer serializer; - private final WorkflowRepository wfRepo; - private final Storage storage; - private final Object[] mutexArray = new Object[2003]; - private final Set currentlyProcessingEarlyResponses = new HashSet<>(); - private boolean started = false; - - public HybridDBStorage(Serializer serializer, WorkflowRepository wfRepo, Storage storage, TimeoutManager timeoutManager, final Executor executor) { - this.ppoolId2queueMap = new ConcurrentHashMap<>(); - this.serializer = serializer; - this.wfRepo = wfRepo; - this.storage = storage; - this.timeoutManager = timeoutManager; - this.executor = executor; - for (int i = 0; i < mutexArray.length; i++) { - mutexArray[i] = new Object(); - } - } - - @Override - public void insert(Workflow wf, Acknowledge ack) throws DuplicateIdException, Exception { - if (wf == null) - throw new NullPointerException(); - - logger.debug("insert({})", wf.getId()); - - startupBlocker.pass(); - - WorkflowInstance cw = new WorkflowInstance(); - cw.id = wf.getId(); - cw.serializedWorkflow = serializer.serializeWorkflow(wf); - cw.ppoolId = wf.getProcessorPoolId(); - cw.prio = wf.getPriority(); - cw.creationTS = wf.getCreationTS(); - cw.state = ProcessingState.ENQUEUED; - cw.classname = wf.getClass().getName(); - - storage.safeWorkflowInstance(cw, true); - - _enqueue(wf.getId(), wf.getProcessorPoolId(), wf.getPriority()); - - if (ack != null) - ack.onSuccess(); - - } - - @Override - public void insert(List> wfs, Acknowledge ack) throws DuplicateIdException, Exception { - for (Workflow wf : wfs) { - insert(wf, ACK); - } - ack.onSuccess(); - } - - @Override - public void insert(Workflow wf, Connection con) throws DuplicateIdException, Exception { - insert(wf, ACK); - } - - @Override - public void insert(List> wfs, Connection con) throws DuplicateIdException, Exception { - for (Workflow wf : wfs) { - insert(wf, ACK); - } - } - - @Override - public void finish(final Workflow w, final Acknowledge _callback) { - logger.debug("finish({})", w.getId()); - final Acknowledge callback = _callback != null ? _callback : ACK; - try { - startupBlocker.pass(); - - final String wfId = w.getId(); - correlationIdMap.removeAll4Workflow(wfId); - final ListenableFuture future = storage.deleteWorkflowInstance(w.getId()); - future.addListener(new Runnable() { - @Override - public void run() { - try { - future.get(); - callback.onSuccess(); - } catch (InterruptedException | ExecutionException e) { - logger.error("finish(" + wfId + ") failed", e); - callback.onException(e); - } - } - }, executor); - } catch (Exception e) { - logger.error("finish failed", e); - callback.onException(e); - } - } - - @Override - public List> dequeue(final String ppoolId, final int max) throws Exception { - logger.debug("dequeue({},{})", ppoolId, max); - final long startTS = System.currentTimeMillis(); - - startupBlocker.pass(); - - final List> wfList = new ArrayList<>(max); - while (wfList.size() < max) { - // block if we read the first element - since we don't want to return an empty list - final QueueElement element = wfList.isEmpty() ? _take(ppoolId) : _poll(ppoolId); - if (element == null) - break; - - synchronized (findMutex(element.wfId)) { - try { - correlationIdMap.removeAll4Workflow(element.wfId); - final WorkflowInstance wi = storage.readWorkflowInstance(element.wfId); - if (wi == null) { - logger.warn("No workflow instance with id {} found in database", element.wfId); - // TODO try again later? - } - else { - Workflow wf = null; - try { - wf = convert2workflow(wi); - } catch (Exception e) { - logger.error("Unable to deserialize workflow instance " + element.wfId + " - setting state to INVALID", e); - storage.updateWorkflowInstanceState(element.wfId, ProcessingState.INVALID); - } - if (wf != null) { - timeoutManager.unregisterTimeout(wi.timeout, wi.id); - wfList.add(wf); - } - } - } catch (Exception e) { - logger.error("Fatal error: dequeue failed for workflow instance " + element.wfId, e); - } - } - } - logger.debug("dequeue({},{}) finished, returning {} elements in {} msec", ppoolId, max, wfList.size(), (System.currentTimeMillis() - startTS)); - return wfList; - } - - private Workflow convert2workflow(WorkflowInstance cw) throws Exception { - if (cw == null) - return null; - - Workflow wf = serializer.deserializeWorkflow(cw.serializedWorkflow, wfRepo); - wf.setId(cw.id); - wf.setProcessorPoolId(cw.ppoolId); - wf.setPriority(cw.prio); - WorkflowAccessor.setCreationTS(wf, cw.creationTS); - WorkflowAccessor.setLastActivityTS(wf, cw.lastModTS); - - if (cw.cid2ResponseMap != null) { - for (Entry e : cw.cid2ResponseMap.entrySet()) { - if (e.getValue() != null) { - Response r = serializer.deserializeResponse(e.getValue()); - wf.putResponse(r); - } - else { - wf.putResponse(new Response<>(e.getKey())); // Set timeout response - } - } - } - return wf; - } - - @Override - public void registerCallback(RegisterCall rc, Acknowledge callback) throws Exception { - logger.debug("registerCallback({})", rc); - - startupBlocker.pass(); - - final String wfId = rc.workflow.getId(); - final WorkflowInstance cw = new WorkflowInstance(); - cw.id = wfId; - cw.state = ProcessingState.WAITING; - cw.prio = rc.workflow.getPriority(); - cw.creationTS = rc.workflow.getCreationTS(); - cw.serializedWorkflow = serializer.serializeWorkflow(rc.workflow); - cw.waitMode = rc.waitMode; - cw.timeout = rc.timeout != null && rc.timeout > 0 ? new Date(System.currentTimeMillis() + rc.timeout) : null; - cw.ppoolId = rc.workflow.getProcessorPoolId(); - cw.cid2ResponseMap = new HashMap(); - for (String cid : rc.correlationIds) { - cw.cid2ResponseMap.put(cid, null); - } - cw.classname = rc.workflow.getClass().getName(); - - storage.safeWorkflowInstance(cw, false); - - correlationIdMap.addCorrelationIds(wfId, rc.correlationIds); - - // check for early responses - // - // 1st make sure that all currently working threads writing early responses do NOT write a response with one of - // our correlationIds - synchronized (currentlyProcessingEarlyResponses) { - for (;;) { - boolean didWait = false; - for (String cid : rc.correlationIds) { - if (currentlyProcessingEarlyResponses.contains(cid)) { - currentlyProcessingEarlyResponses.wait(); - didWait = true; - } - } - if (!didWait) - break; - } - } - // 2nd read early responses and connect them to the workflow instance - boolean enqueued = false; - for (String cid : rc.correlationIds) { - Response response = serializer.deserializeResponse(storage.readEarlyResponse(cid)); - if (response != null) { - logger.debug("found early response with correlationId {} for workflow {} - doing notify...", cid, wfId); - if (notifyInternal(response, ACK)) { - enqueued = true; - } - storage.deleteEarlyResponse(cid); - } - } - - if (cw.timeout != null && !enqueued) { - timeoutManager.registerTimeout(cw.timeout, wfId, new Runnable() { - @Override - public void run() { - onTimeout(wfId); - } - }); - } - - callback.onSuccess(); - } - - @Override - public void notify(Response response, Acknowledge ack) throws Exception { - logger.debug("notify({})", response); - - startupBlocker.pass(); - - notifyInternal(response, ack); - } - - /** - * - * @param response - * @param ack - * @return true, if the corresponding workflow instance has been enqueued due to this response - * @throws Exception - */ - private boolean notifyInternal(Response response, Acknowledge ack) throws Exception { - logger.debug("notifyInternal({})", response); - - try { - final String cid = response.getCorrelationId(); - final String wfId = correlationIdMap.getWorkflowId(cid); - - if (wfId != null) { - // we have to take care of concurrent notifies for the same workflow instance - // but we don't want to block everything - it's sufficient to block this workflows id (more or less...) - synchronized (findMutex(wfId)) { - // check if this workflow instance has just been dequeued - in this case we do not find the - // correlationId any more... - if (correlationIdMap.getWorkflowId(cid) != null) { - WorkflowInstance cw = storage.readWorkflowInstance(wfId); - if (cw.cid2ResponseMap.containsKey(cid)) { - cw.cid2ResponseMap.put(cid, serializer.serializeResponse(response)); - } - final boolean timeoutOccured = cw.timeout != null && cw.timeout.getTime() <= System.currentTimeMillis(); - final boolean enqueue = cw.state == ProcessingState.WAITING && (timeoutOccured || cw.waitMode == WaitMode.FIRST || cw.waitMode == WaitMode.ALL && cw.cid2ResponseMap.size() == 1 || cw.waitMode == WaitMode.ALL && allResponsesAvailable(cw)); - - if (enqueue) { - cw.state = ProcessingState.ENQUEUED; - } - - storage.safeWorkflowInstance(cw, false); - - if (enqueue) { - _enqueue(cw.id, cw.ppoolId, cw.prio); - } - - ack.onSuccess(); - return enqueue; - } - } - } - } catch (Exception e) { - ack.onException(e); - throw e; - } - - handleEarlyResponse(response, ack); - return false; - } - - private void onTimeout(final String wfId) { - logger.debug("onTimeout(wfId={})", wfId); - try { - synchronized (findMutex(wfId)) { - // check if this workflow instance has just been dequeued - in this case we do not find the - // correlationId any more... - if (correlationIdMap.containsWorkflowId(wfId)) { - final WorkflowInstance cw = storage.readWorkflowInstance(wfId); - logger.debug("workflow instance={}", cw); - final boolean enqueue = cw.state == ProcessingState.WAITING; - - if (enqueue) { - cw.state = ProcessingState.ENQUEUED; - storage.safeWorkflowInstance(cw, false); - _enqueue(cw.id, cw.ppoolId, cw.prio); - } - } - } - } catch (Exception e) { - logger.error("onTimeout failed for wfId " + wfId, e); - } - } - - private void handleEarlyResponse(final Response response, final Acknowledge ack) throws Exception { - synchronized (currentlyProcessingEarlyResponses) { - currentlyProcessingEarlyResponses.add(response.getCorrelationId()); - } - final ListenableFuture future = storage.safeEarlyResponse(response.getCorrelationId(), serializer.serializeResponse(response)); - future.addListener(new Runnable() { - @Override - public void run() { - try { - future.get(); - ack.onSuccess(); - } - catch (Exception e) { - logger.error("safeEarlyResponse failed", e); - ack.onException(e); - } - finally { - synchronized (currentlyProcessingEarlyResponses) { - currentlyProcessingEarlyResponses.remove(response.getCorrelationId()); - currentlyProcessingEarlyResponses.notifyAll(); - } - } - } - }, executor); - } - - @Override - public void notify(List> responses, Acknowledge ack) throws Exception { - for (Response r : responses) { - notify(r, ACK); - } - ack.onSuccess(); - } - - @Override - public void notify(List> responses, Connection c) throws Exception { - for (Response r : responses) { - notify(r, ACK); - } - } - - @Override - public synchronized void startup() { - if (started) - return; - - logger.info("Starting up..."); - try { - storage.initialize(new HybridDBStorageAccessor() { - @Override - public void registerCorrelationId(String correlationId, String wfId) { - _registerCorrelationId(correlationId, wfId); - } - - @Override - public void enqueue(String wfId, String ppoolId, int prio) { - _enqueue(wfId, ppoolId, prio); - } - }, Runtime.getRuntime().availableProcessors()); - } catch (RuntimeException e) { - logger.error("startup failed", e); - throw e; - - } catch (Exception e) { - logger.error("startup failed", e); - throw new CopperRuntimeException("startup failed", e); - } - - started = true; - startupBlocker.unblock(); - - logger.info("Startup finished!"); - } - - @Override - public void shutdown() { - // empty - } - - @Override - public void error(Workflow w, Throwable t, Acknowledge callback) { - try { - startupBlocker.pass(); - correlationIdMap.removeAll4Workflow(w.getId()); - storage.updateWorkflowInstanceState(w.getId(), ProcessingState.ERROR); - if (callback != null) - callback.onSuccess(); - } catch (Exception e) { - logger.error("error failed", e); - if (callback != null) - callback.onException(e); - } - } - - @Override - public void restart(String workflowInstanceId) throws Exception { - startupBlocker.pass(); - - WorkflowInstance cw = storage.readWorkflowInstance(workflowInstanceId); - if (cw == null) - throw new CopperRuntimeException("No workflow found with id " + workflowInstanceId); - if (cw.state != ProcessingState.ERROR) - throw new CopperRuntimeException("Workflow found with id " + workflowInstanceId + " is not in state ERROR"); - _enqueue(cw.id, cw.ppoolId, cw.prio); - } - - @Override - public void setRemoveWhenFinished(boolean removeWhenFinished) { - throw new UnsupportedOperationException(); - } - - @Override - public void restartAll() throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public void restartFiltered(WorkflowInstanceFilter filter) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public void deleteBroken(String workflowInstanceId) throws Exception { - throw new UnsupportedOperationException(); - // TODO: Implement this here... - } - - @Override - public void deleteWaiting(String workflowInstanceId) throws Exception { - throw new UnsupportedOperationException(); - // TODO: Implement this here... - } - - - @Override - public void deleteFiltered(WorkflowInstanceFilter filter) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public Workflow read(String workflowInstanceId) throws Exception { - return convert2workflow(storage.readWorkflowInstance(workflowInstanceId)); - } - - void _enqueue(String wfId, String ppoolId, int prio) { - logger.trace("enqueue(wfId={}, ppoolId={}, prio={})", wfId, ppoolId, prio); - ConcurrentSkipListSet queue = _findQueue(ppoolId); - synchronized (queue) { - boolean rv = queue.add(new QueueElement(wfId, prio)); - assert rv : "queue already contains workflow id " + wfId; - queue.notify(); - } - } - - QueueElement _poll(String ppoolId) { - logger.trace("_poll({})", ppoolId); - ConcurrentSkipListSet queue = _findQueue(ppoolId); - QueueElement qe = queue.pollFirst(); - if (qe != null) { - logger.debug("dequeued for ppoolId={}: wfId={}", ppoolId, qe.wfId); - } - return qe; - } - - QueueElement _take(String ppoolId) throws InterruptedException { - logger.trace("_take({})", ppoolId); - ConcurrentSkipListSet queue = _findQueue(ppoolId); - synchronized (queue) { - for (;;) { - QueueElement qe = queue.pollFirst(); - if (qe != null) { - logger.debug("dequeued for ppoolId={}: wfId={}", ppoolId, qe.wfId); - return qe; - } - queue.wait(10L); - } - } - } - - private ConcurrentSkipListSet _findQueue(final String ppoolId) { - ConcurrentSkipListSet queue = ppoolId2queueMap.get(ppoolId); - if (queue != null) - return queue; - synchronized (ppoolId2queueMap) { - queue = ppoolId2queueMap.get(ppoolId); - if (queue != null) - return queue; - queue = new ConcurrentSkipListSet<>(new QueueElementComparator()); - ppoolId2queueMap.put(ppoolId, queue); - return queue; - } - } - - private void _registerCorrelationId(String correlationId, String wfId) { - correlationIdMap.addCorrelationId(wfId, correlationId); - } - - private boolean allResponsesAvailable(WorkflowInstance cw) { - for (Entry e : cw.cid2ResponseMap.entrySet()) { - if (e.getValue() == null) - return false; - } - return true; - } - - private Object findMutex(String id) { - long hash = id.hashCode(); - hash = Math.abs(hash); - int x = (int) (hash % mutexArray.length); - return mutexArray[x]; - } - - @Override - public List> queryAllActive(final String className, final int max) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public int queryQueueSize(String processorPoolId) throws Exception { - final ConcurrentSkipListSet queue = ppoolId2queueMap.get(Objects.requireNonNull(processorPoolId)); - return queue == null ? 0 : queue.size(); - } - - @Override - public List> queryWorkflowInstances(WorkflowInstanceFilter filter) throws Exception { - List> resultList = new ArrayList<>(); - List list = storage.queryWorkflowInstances(filter); - for (WorkflowInstance wi : list) { - try { - resultList.add(convert2workflow(wi)); - } - catch(Exception e) { - logger.error("Failed to convert workflow instance "+wi.id, e); - } - } - return resultList; - } - - @Override - public String queryObjectState(String id) throws Exception { - throw new UnsupportedOperationException(); - } - - - @Override - public int countWorkflowInstances(final WorkflowInstanceFilter filter) throws Exception { - return storage.countWorkflowInstances(filter); - } - - @Override - public List queryAuditTrailInstances(AuditTrailInstanceFilter filter) throws Exception { - throw new UnsupportedOperationException(); - // TODO: Implement this here... - } - - @Override - public String queryAuditTrailMessage(long id) throws Exception { - throw new UnsupportedOperationException(); - } - - @Override - public int countAuditTrailInstances(AuditTrailInstanceFilter filter) throws Exception { - throw new UnsupportedOperationException(); - // TODO: Implement this here... - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorageAccessor.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorageAccessor.java deleted file mode 100644 index a57069b5..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridDBStorageAccessor.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -/** - * Used just for initialization of {@link HybridDBStorage} during startup - * - * @author austermann - * - */ -public interface HybridDBStorageAccessor { - - public void enqueue(String wfId, String ppoolId, int prio); - - public void registerCorrelationId(String correlationId, String wfId); - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridEngineFactory.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridEngineFactory.java deleted file mode 100644 index 575e5d96..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridEngineFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.List; - -import org.copperengine.core.DependencyInjector; -import org.copperengine.core.persistent.ScottyDBStorageInterface; -import org.copperengine.core.persistent.txn.TransactionController; -import org.copperengine.ext.persistent.AbstractPersistentEngineFactory; -import org.slf4j.Logger; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - -public abstract class HybridEngineFactory extends AbstractPersistentEngineFactory { - - private static final Logger logger = org.slf4j.LoggerFactory.getLogger(HybridEngineFactory.class); - - protected final Supplier timeoutManager; - protected final Supplier storage; - - public HybridEngineFactory(List wfPackges) { - super(wfPackges); - - timeoutManager = Suppliers.memoize(new Supplier() { - @Override - public TimeoutManager get() { - logger.info("Creating TimeoutManager..."); - return createTimeoutManager(); - } - }); - storage = Suppliers.memoize(new Supplier() { - @Override - public Storage get() { - logger.info("Creating Storage..."); - return createStorage(); - } - }); - } - - protected abstract Storage createStorage(); - - @Override - protected ScottyDBStorageInterface createDBStorage() { - return new HybridDBStorage(serializer.get(), workflowRepository.get(), storage.get(), timeoutManager.get(), executorService.get()); - } - - @Override - protected TransactionController createTransactionController() { - return new HybridTransactionController(); - } - - protected TimeoutManager createTimeoutManager() { - return new DefaultTimeoutManager().startup(); - } - - public void destroyEngine() { - super.destroyEngine(); - - timeoutManager.get().shutdown(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridTransactionController.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridTransactionController.java deleted file mode 100644 index eac40474..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/HybridTransactionController.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import org.copperengine.core.persistent.txn.DatabaseTransaction; -import org.copperengine.core.persistent.txn.Transaction; -import org.copperengine.core.persistent.txn.TransactionController; - -/** - * empty implementation of the {@link TransactionController} interface, as the HybridDBStorage does NOT support - * transactions. - * - * @author austermann - * - */ -public class HybridTransactionController implements TransactionController { - - @Override - public T run(DatabaseTransaction txn) throws Exception { - return txn.run(null); - } - - @Override - public T run(Transaction txn) throws Exception { - return txn.run(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElement.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElement.java deleted file mode 100644 index 36e0168e..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElement.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -class QueueElement { - - public final String wfId; - public final int prio; - public final long enqueueTS = System.currentTimeMillis(); - - public QueueElement(String wfId, int prio) { - this.wfId = wfId; - this.prio = prio; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((wfId == null) ? 0 : wfId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - QueueElement other = (QueueElement) obj; - if (wfId == null) { - if (other.wfId != null) - return false; - } else if (!wfId.equals(other.wfId)) - return false; - return true; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElementComparator.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElementComparator.java deleted file mode 100644 index 335cb3ab..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/QueueElementComparator.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.Comparator; - -class QueueElementComparator implements Comparator { - @Override - public int compare(QueueElement o1, QueueElement o2) { - if (o1.prio != o2.prio) { - return o1.prio - o2.prio; - } else { - if (o1.enqueueTS == o2.enqueueTS) { - return o1.wfId.compareTo(o2.wfId); - } - else if (o1.enqueueTS > o2.enqueueTS) { - return 1; - } - else { - return -1; - } - } - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/Storage.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/Storage.java deleted file mode 100644 index 121d2ff1..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/Storage.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.List; - -import org.copperengine.core.ProcessingState; -import org.copperengine.management.model.WorkflowInstanceFilter; - -import com.google.common.util.concurrent.ListenableFuture; - -/** - * Map-like persistent storage for a {@link HybridDBStorage} - * - * @author austermann - * - */ -public interface Storage { - - public void safeWorkflowInstance(WorkflowInstance cw, boolean initialInsert) throws Exception; - - public ListenableFuture deleteWorkflowInstance(String wfId) throws Exception; - - public WorkflowInstance readWorkflowInstance(String wfId) throws Exception; - - public void initialize(HybridDBStorageAccessor internalStorageAccessor, int numberOfThreads) throws Exception; - - public ListenableFuture safeEarlyResponse(String correlationId, String serializedResponse) throws Exception; - - public String readEarlyResponse(String correlationId) throws Exception; - - public ListenableFuture deleteEarlyResponse(String correlationId) throws Exception; - - public ListenableFuture updateWorkflowInstanceState(String wfId, ProcessingState state) throws Exception; - - public List queryWorkflowInstances(WorkflowInstanceFilter filter) throws Exception; - - public int countWorkflowInstances(WorkflowInstanceFilter filter) throws Exception; - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/StorageCache.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/StorageCache.java deleted file mode 100644 index 850a903f..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/StorageCache.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.copperengine.core.persistent.hybrid; - -import java.lang.ref.SoftReference; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.commons.lang.NullArgumentException; -import org.copperengine.core.ProcessingState; -import org.copperengine.core.Workflow; -import org.copperengine.management.model.WorkflowInstanceFilter; -import org.slf4j.Logger; - -import com.google.common.util.concurrent.ListenableFuture; - -public class StorageCache implements Storage { - - private static final Logger logger = org.slf4j.LoggerFactory.getLogger(StorageCache.class); - - private final Storage delegate; - private final Map> wfCache; - private final Map> earCache; - private final CacheStats cacheStatsWfCache = new CacheStats(); - private final CacheStats cacheStatsEarCache = new CacheStats(); - - public StorageCache(Storage delegate) { - if (delegate == null) - throw new NullArgumentException("delegate"); - this.delegate = delegate; - - wfCache = new ConcurrentHashMap<>(); - earCache = new ConcurrentHashMap<>(); - } - - public void logCacheStats() { - logger.info("cacheStatsWfCache = {}", cacheStatsWfCache); - logger.info("cacheStatsEarCache = {}", cacheStatsEarCache); - } - - @Override - public void safeWorkflowInstance(WorkflowInstance wfi, boolean initialInsert) throws Exception { - wfCache.put(wfi.id, new SoftReference(wfi)); - delegate.safeWorkflowInstance(wfi, initialInsert); - } - - @Override - public ListenableFuture deleteWorkflowInstance(String wfId) throws Exception { - wfCache.remove(wfId); - return delegate.deleteWorkflowInstance(wfId); - } - - @Override - public WorkflowInstance readWorkflowInstance(String wfId) throws Exception { - SoftReference entry = wfCache.get(wfId); - if (entry != null) { - WorkflowInstance wfi = entry.get(); - if (wfi != null) { - cacheStatsWfCache.incNumberOfReads(true); - return wfi; - } - } - WorkflowInstance wfi = delegate.readWorkflowInstance(wfId); - if (wfi != null) { - wfCache.put(wfi.id, new SoftReference(wfi)); - } - cacheStatsWfCache.incNumberOfReads(false); - return wfi; - } - - @Override - public void initialize(HybridDBStorageAccessor internalStorageAccessor, int numberOfThreads) throws Exception { - delegate.initialize(internalStorageAccessor, numberOfThreads); - } - - @Override - public ListenableFuture safeEarlyResponse(String correlationId, String serializedResponse) throws Exception { - earCache.put(correlationId, new SoftReference(serializedResponse)); - return delegate.safeEarlyResponse(correlationId, serializedResponse); - } - - @Override - public String readEarlyResponse(String correlationId) throws Exception { - final SoftReference entry = earCache.get(correlationId); - if (entry != null) { - String resp = entry.get(); - if (resp != null) { - cacheStatsEarCache.incNumberOfReads(true); - return resp; - } - } - cacheStatsEarCache.incNumberOfReads(false); - return delegate.readEarlyResponse(correlationId); - } - - @Override - public ListenableFuture deleteEarlyResponse(String correlationId) throws Exception { - earCache.remove(correlationId); - return delegate.deleteEarlyResponse(correlationId); - } - - @Override - public ListenableFuture updateWorkflowInstanceState(String wfId, ProcessingState state) throws Exception { - wfCache.remove(wfId); - return delegate.updateWorkflowInstanceState(wfId, state); - } - - @Override - public List queryWorkflowInstances(WorkflowInstanceFilter filter) throws Exception { - return delegate.queryWorkflowInstances(filter); - } - - @Override - public int countWorkflowInstances(WorkflowInstanceFilter filter) throws Exception { - return delegate.countWorkflowInstances(filter); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutManager.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutManager.java deleted file mode 100644 index 407ee872..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutManager.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.Date; - -/** - * public interface for a timeout manager used in a {@link HybridDBStorage}. - * The timeout manager is responsible to wake up waiting workflow instances in case of a timeout. - * - * @author austermann - */ -public interface TimeoutManager { - - public void registerTimeout(Date timeoutTS, String workflowId, Runnable onTimeout); - - public void unregisterTimeout(Date timeoutTS, String workflowId); - - public TimeoutManager startup(); - - public TimeoutManager shutdown(); -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutSlot.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutSlot.java deleted file mode 100644 index 6516b35a..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/TimeoutSlot.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.HashMap; -import java.util.Map; - -/** - * Internally used class. - * - * @author austermann - */ -final class TimeoutSlot { - - private final long timeoutTS; - private final Map wfId2RunnableMap = new HashMap<>(); - - public TimeoutSlot(long timeoutTS) { - assert timeoutTS > 0; - this.timeoutTS = timeoutTS; - } - - public long getTimeoutTS() { - return timeoutTS; - } - - public Map getWfId2RunnableMap() { - return wfId2RunnableMap; - } -} \ No newline at end of file diff --git a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/WorkflowInstance.java b/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/WorkflowInstance.java deleted file mode 100644 index 99e6d64e..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/java/org/copperengine/core/persistent/hybrid/WorkflowInstance.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.Date; -import java.util.Map; - -import org.copperengine.core.ProcessingState; -import org.copperengine.core.WaitMode; -import org.copperengine.core.persistent.SerializedWorkflow; - -/** - * DTO representation of a copper workflow instance used in {@link Storage} - * - * @author austermann - * - */ -public class WorkflowInstance { - - public WorkflowInstance() { - } - - public String id; - public String ppoolId; - public int prio; - public Date creationTS; - public SerializedWorkflow serializedWorkflow; - public Map cid2ResponseMap; - public WaitMode waitMode; - public Date timeout; - public ProcessingState state; - public Date lastModTS; - public String classname; - - @Override - public String toString() { - return "WorkflowInstance [id=" + id + ", ppoolId=" + ppoolId + ", prio=" + prio + ", creationTS=" + creationTS + ", serializedWorkflow=" + serializedWorkflow + ", cid2ResponseMap=" + cid2ResponseMap + ", waitMode=" + waitMode + ", timeout=" + timeout + ", state=" + state + ", lastModTS=" + lastModTS + ", classname=" + classname + "]"; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/main/resources/org/copperengine/core/persistent/cassandra/copper-schema.cql b/projects/copper-cassandra/cassandra-storage/src/main/resources/org/copperengine/core/persistent/cassandra/copper-schema.cql deleted file mode 100644 index 3dc1f914..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/main/resources/org/copperengine/core/persistent/cassandra/copper-schema.cql +++ /dev/null @@ -1,53 +0,0 @@ --- --- Copyright 2002-2017 SCOOP Software GmbH --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -CREATE TABLE COP_WFI_ID ( - ID varchar, - primary key (ID) -); - -alter table COP_WFI_ID with GC_GRACE_SECONDS = 1; - - -CREATE TABLE COP_WORKFLOW_INSTANCE ( - ID varchar, - STATE varchar, - PPOOL_ID varchar, - PRIO int, - CREATION_TS timestamp, - WAIT_MODE varchar, - TIMEOUT timestamp, - RESPONSE_MAP_JSON varchar, - DATA varchar, - OBJECT_STATE varchar, - LAST_MOD_TS timestamp, - CLASSNAME varchar, - primary key (ID) -); - -CREATE TABLE COP_EARLY_RESPONSE ( - CORRELATION_ID varchar, - RESPONSE varchar, - primary key (CORRELATION_ID) -); - - - --- 5 days -alter table COP_WORKFLOW_INSTANCE with GC_GRACE_SECONDS = 432000; - --- 2 days -alter table COP_EARLY_RESPONSE with GC_GRACE_SECONDS = 172800; \ No newline at end of file diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/module-info.test b/projects/copper-cassandra/cassandra-storage/src/test/java/module-info.test deleted file mode 100644 index 7d59c5db..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/module-info.test +++ /dev/null @@ -1,5 +0,0 @@ ---add-reads - org.copperengine.cassandra.storage=org.mockito - ---add-exports - org.copperengine.cassandra.storage/org.copperengine.core.persistent.cassandra.workflows=ALL-UNNAMED diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactoryUsage.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactoryUsage.java deleted file mode 100644 index 7b3b67cf..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraEngineFactoryUsage.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.Arrays; - -import org.copperengine.core.util.PojoDependencyInjector; -import org.junit.Test; - -@org.junit.Ignore -public class CassandraEngineFactoryUsage extends CassandraTest { - - @Test - public void test() throws Exception { - CassandraEngineFactory engineFactory = new CassandraEngineFactory(Arrays.asList("package.of.copper.workflow.classes")) { - @Override - protected PojoDependencyInjector createDependencyInjector() { - return new PojoDependencyInjector(); - } - }; - engineFactory.getEngine().startup(); - Thread.sleep(500); - engineFactory.getEngine().shutdown(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImplTest.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImplTest.java deleted file mode 100644 index 46b3b4d8..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraSessionManagerImplTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.Collections; - -import org.junit.Assume; -import org.junit.Test; - -public class CassandraSessionManagerImplTest extends CassandraTest { - - @Test() - public void test() { - Assume.assumeTrue(factory != null); - CassandraSessionManagerImpl cassandraSessionManagerImpl = new CassandraSessionManagerImpl(Collections.singletonList("localhost"), CassandraTest.CASSANDRA_PORT, "copper"); - cassandraSessionManagerImpl.startup(); - cassandraSessionManagerImpl.shutdown(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraTest.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraTest.java deleted file mode 100644 index d58601e4..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/CassandraTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -/** - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import org.cassandraunit.utils.EmbeddedCassandraServerHelper; -import org.junit.BeforeClass; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Cluster.Builder; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.exceptions.NoHostAvailableException; - -public class CassandraTest { - - private static final Logger logger = LoggerFactory.getLogger(CassandraTest.class); - - public static final int CASSANDRA_PORT = 9042; - - protected static UnitTestCassandraEngineFactory factory; - - @BeforeClass - public synchronized static void setUpBeforeClass() throws Exception { - if (factory == null) { -// logger.info("Starting embedded cassandra..."); -// EmbeddedCassandraServerHelper.startEmbeddedCassandra("unittest-cassandra.yaml", "./build/cassandra"); -// Thread.sleep(100); -// logger.info("Successfully started embedded cassandra."); - - final Cluster cluster = new Builder().addContactPoint("localhost").withPort(CASSANDRA_PORT).build(); -// final Session session = cluster.newSession(); -// session.execute("CREATE KEYSPACE copper WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"); - - factory = new UnitTestCassandraEngineFactory(false); - factory.setCassandraPort(CASSANDRA_PORT); - try { - factory.getEngine().startup(); - } catch (NoHostAvailableException e) { - factory = null; - } - } - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/DummyResponseSender.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/DummyResponseSender.java deleted file mode 100644 index a17bd76b..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/DummyResponseSender.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.Acknowledge; -import org.copperengine.core.ProcessingEngine; -import org.copperengine.core.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DummyResponseSender { - - private static final Logger logger = LoggerFactory.getLogger(DummyResponseSender.class); - - private final ScheduledExecutorService exec; - private final ProcessingEngine engine; - - public DummyResponseSender(ScheduledExecutorService exec, ProcessingEngine engine) { - super(); - this.exec = exec; - this.engine = engine; - } - - public void foo(final String cid, final int delay, final TimeUnit timeUnit) { - if (delay == 0) { - engine.notify(new Response(cid, "foo" + cid, null), new Acknowledge.BestEffortAcknowledge()); - } - else { - exec.schedule(new Runnable() { - @Override - public void run() { - logger.debug("notify for cid={}", cid); - engine.notify(new Response(cid, "foo" + cid, null), new Acknowledge.BestEffortAcknowledge()); - } - }, delay, timeUnit); - } - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestData.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestData.java deleted file mode 100644 index ca9ce83b..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestData.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.io.Serializable; - -public class TestData implements Serializable { - - private static final long serialVersionUID = 1L; - - public String id; - public String someData; - - public TestData() { - } - - public TestData(String id, String someData) { - this.id = id; - this.someData = someData; - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestWorkflowCassandraTest.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestWorkflowCassandraTest.java deleted file mode 100644 index 04488107..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/TestWorkflowCassandraTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.WorkflowInstanceDescr; -import org.copperengine.management.model.WorkflowInfo; -import org.copperengine.management.model.WorkflowInstanceFilter; -import org.junit.Assume; -import org.junit.Test; - -public class TestWorkflowCassandraTest extends CassandraTest { - - @Test - public void testParallel() throws Exception { - Assume.assumeTrue(factory != null); - List cids = new ArrayList<>(); - for (int i = 0; i < 50; i++) { - final String cid = factory.getEngine().createUUID(); - final TestData data = new TestData(cid, "foo"); - final WorkflowInstanceDescr wfid = new WorkflowInstanceDescr("org.copperengine.core.persistent.cassandra.workflows.TestWorkflow", data, cid, 1, null); - factory.getEngine().run(wfid); - cids.add(cid); - } - for (String cid : cids) { - Object response = factory.backchannel.get().wait(cid, 10000, TimeUnit.MILLISECONDS); - org.junit.Assert.assertNotNull("no response for workflow instance " + cid, response); - org.junit.Assert.assertEquals("OK", response); - } - Thread.sleep(250); - WorkflowInstanceFilter filter = new WorkflowInstanceFilter(); - List result = factory.getEngine().queryWorkflowInstances(filter); - assertEquals(0, result.size()); - } - - @Test - public void testSerial() throws Exception { - Assume.assumeTrue(factory != null); - for (int i = 0; i < 3; i++) { - final String cid = factory.getEngine().createUUID(); - final TestData data = new TestData(cid, "foo"); - final WorkflowInstanceDescr wfid = new WorkflowInstanceDescr("org.copperengine.core.persistent.cassandra.workflows.TestWorkflow", data, cid, 1, null); - factory.getEngine().run(wfid); - Object response = factory.backchannel.get().wait(cid, 10000, TimeUnit.MILLISECONDS); - org.junit.Assert.assertNotNull(response); - org.junit.Assert.assertEquals("OK", response); - } - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/UnitTestCassandraEngineFactory.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/UnitTestCassandraEngineFactory.java deleted file mode 100644 index 0fd7c292..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/UnitTestCassandraEngineFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.cassandra; - -import java.util.Arrays; - -import org.copperengine.core.util.Backchannel; -import org.copperengine.core.util.BackchannelDefaultImpl; -import org.copperengine.core.util.PojoDependencyInjector; -import org.copperengine.ext.util.Supplier2Provider; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - -public class UnitTestCassandraEngineFactory extends CassandraEngineFactory { - - public final Supplier backchannel; - public final Supplier dummyResponseSender; - protected final boolean truncate; - - public UnitTestCassandraEngineFactory(boolean truncate) { - super(Arrays.asList("org.copperengine.core.persistent.cassandra.workflows")); - this.truncate = truncate; - - backchannel = Suppliers.memoize(new Supplier() { - @Override - public Backchannel get() { - return new BackchannelDefaultImpl(); - } - }); - dummyResponseSender = Suppliers.memoize(new Supplier() { - @Override - public DummyResponseSender get() { - return new DummyResponseSender(scheduledExecutorService.get(), engine.get()); - } - }); - dependencyInjector.get().register("dummyResponseSender", new Supplier2Provider<>(dummyResponseSender)); - dependencyInjector.get().register("backchannel", new Supplier2Provider<>(backchannel)); - } - - @Override - protected CassandraSessionManager createCassandraSessionManager() { - final CassandraSessionManager csm = super.createCassandraSessionManager(); - if (truncate) { - csm.getSession().execute("truncate COP_WORKFLOW_INSTANCE"); - csm.getSession().execute("truncate COP_EARLY_RESPONSE"); - csm.getSession().execute("truncate COP_WFI_ID"); - } - return csm; - } - - @Override - protected PojoDependencyInjector createDependencyInjector() { - return new PojoDependencyInjector(); - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/workflows/TestWorkflow.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/workflows/TestWorkflow.java deleted file mode 100644 index f2423294..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/cassandra/workflows/TestWorkflow.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.copperengine.core.persistent.cassandra.workflows; - -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.AutoWire; -import org.copperengine.core.Interrupt; -import org.copperengine.core.Response; -import org.copperengine.core.WaitMode; -import org.copperengine.core.persistent.PersistentWorkflow; -import org.copperengine.core.persistent.cassandra.DummyResponseSender; -import org.copperengine.core.persistent.cassandra.TestData; -import org.copperengine.core.util.Backchannel; -import org.junit.Assert; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestWorkflow extends PersistentWorkflow { - - private static final long serialVersionUID = 1L; - private static final Logger logger = LoggerFactory.getLogger(TestWorkflow.class); - private static final int DEFAULT_TIMEOUT = 5000; - - private transient DummyResponseSender dummyResponseSender; - private transient Backchannel backchannel; - - @AutoWire(beanId = "backchannel") - public void setBackchannel(Backchannel backchannel) { - this.backchannel = backchannel; - } - - @AutoWire(beanId = "dummyResponseSender") - public void setDummyResponseSender(DummyResponseSender dummyResponseSender) { - this.dummyResponseSender = dummyResponseSender; - } - - @Override - public void main() throws Interrupt { - try { - logger.info("started"); - - logger.info("Testing delayed response..."); - delayedResponse(); - - logger.info("Testing early response..."); - earlyResponse(); - - logger.info("Testing timeout response..."); - timeoutResponse(); - - logger.info("Testing delayed multi response..."); - delayedMultiResponse(); - - backchannel.notify(getData().id, "OK"); - logger.info("finished"); - } catch (Exception e) { - logger.error("workflow failed", e); - backchannel.notify(getData().id, e); - System.exit(0); - } catch (AssertionError e) { - logger.error("workflow failed", e); - backchannel.notify(getData().id, e); - System.exit(0); - } - } - - private void delayedResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - dummyResponseSender.foo(cid, 100, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid); - checkResponse(cid); - } - - private void earlyResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - dummyResponseSender.foo(cid, 0, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid); - checkResponse(cid); - } - - private void checkResponse(final String cid) { - Response r = getAndRemoveResponse(cid); - Assert.assertNotNull("Response is null for wfid=" + getId() + " and cid=" + cid, r); - Assert.assertEquals("Unexpected response for wfid=" + getId() + " and cid=" + cid, "foo" + cid, r.getResponse()); - } - - private void timeoutResponse() throws Interrupt { - final String cid = getEngine().createUUID(); - wait(WaitMode.ALL, 100, cid); - Response r = getAndRemoveResponse(cid); - Assert.assertNotNull(r); - Assert.assertNull(r.getResponse()); - Assert.assertTrue(r.isTimeout()); - } - - private void delayedMultiResponse() throws Interrupt { - final String cid1 = getEngine().createUUID(); - final String cid2 = getEngine().createUUID(); - final String cid3 = getEngine().createUUID(); - dummyResponseSender.foo(cid1, 50, TimeUnit.MILLISECONDS); - dummyResponseSender.foo(cid2, 100, TimeUnit.MILLISECONDS); - dummyResponseSender.foo(cid3, 150, TimeUnit.MILLISECONDS); - wait(WaitMode.ALL, DEFAULT_TIMEOUT, cid1, cid2, cid3); - checkResponse(cid1); - checkResponse(cid2); - checkResponse(cid3); - - } - -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/hybrid/HybridDBStorageTest.java b/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/hybrid/HybridDBStorageTest.java deleted file mode 100644 index f8029ee5..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/java/org/copperengine/core/persistent/hybrid/HybridDBStorageTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.core.persistent.hybrid; - -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.copperengine.core.common.WorkflowRepository; -import org.copperengine.core.persistent.StandardJavaSerializer; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.Mockito; - -public class HybridDBStorageTest { - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - } - - @Test - public void test_enqueue_dequeue_serial() { - final String ppoolId = "DEFAULT"; - final int max = 100; - final HybridDBStorage dbStorage = new HybridDBStorage(new StandardJavaSerializer(), Mockito.mock(WorkflowRepository.class), Mockito.mock(Storage.class), Mockito.mock(TimeoutManager.class), Mockito.mock(Executor.class)); - for (int i = 0; i < max; i++) { - dbStorage._enqueue(Integer.toString(i), ppoolId, max - i); - } - for (int i = 0; i < max; i++) { - QueueElement qe = dbStorage._poll(ppoolId); - Assert.assertNotNull(qe); - } - } - - @Test - public void test_enqueue_dequeue_parallel() throws Exception { - final int numberOfThreads = Runtime.getRuntime().availableProcessors(); - final String ppoolId = "DEFAULT"; - final int max = 10000; - final HybridDBStorage dbStorage = new HybridDBStorage(new StandardJavaSerializer(), Mockito.mock(WorkflowRepository.class), Mockito.mock(Storage.class), Mockito.mock(TimeoutManager.class), Mockito.mock(Executor.class)); - ExecutorService exec = Executors.newFixedThreadPool(numberOfThreads); - - for (int i = 0; i < max; i++) { - exec.execute(new Runnable() { - @Override - public void run() { - dbStorage._enqueue(UUID.randomUUID().toString(), ppoolId, 1); - } - }); - } - exec.shutdown(); - exec.awaitTermination(10000, TimeUnit.MILLISECONDS); - - final AtomicInteger counter = new AtomicInteger(0); - exec = Executors.newFixedThreadPool(numberOfThreads); - for (int i = 0; i < numberOfThreads; i++) { - exec.execute(new Runnable() { - @Override - public void run() { - for (;;) { - QueueElement qe = dbStorage._poll(ppoolId); - if (qe == null) { - break; - } - else { - counter.incrementAndGet(); - } - } - } - }); - } - exec.shutdown(); - exec.awaitTermination(10000, TimeUnit.MILLISECONDS); - - Assert.assertEquals(max, counter.intValue()); - } -} diff --git a/projects/copper-cassandra/cassandra-storage/src/test/resources/log4j.properties b/projects/copper-cassandra/cassandra-storage/src/test/resources/log4j.properties deleted file mode 100644 index 0f50bae1..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/resources/log4j.properties +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2002-2015 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Set root logger level to DEBUG and its only appender to A1. -log4j.rootLogger=INFO, A2, A1 - -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender -log4j.appender.A2=org.apache.log4j.FileAppender -log4j.appender.StatisticsAppender=org.apache.log4j.FileAppender - -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n -#log4j.appender.A1.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss,SSS} [%t] %-5p %c{1} - %m%n - -log4j.appender.A2.File=coppper-cassandra-test.log -log4j.appender.A2.layout=org.apache.log4j.PatternLayout -log4j.appender.A2.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n -log4j.appender.A2.append=false - -#log4j.logger.org.copperengine=INFO -log4j.logger.org.copperengine.core.instrument=INFO -log4j.logger.org.copperengine.core.wfrepo=INFO -#log4j.logger.org.copperengine.core.persistent.cassandra=INFO -log4j.logger.org.copperengine.core.persistent.hybrid=INFO - - diff --git a/projects/copper-cassandra/cassandra-storage/src/test/resources/unittest-cassandra.yaml b/projects/copper-cassandra/cassandra-storage/src/test/resources/unittest-cassandra.yaml deleted file mode 100644 index 6af22bc1..00000000 --- a/projects/copper-cassandra/cassandra-storage/src/test/resources/unittest-cassandra.yaml +++ /dev/null @@ -1,602 +0,0 @@ -# -# Copyright 2002-2017 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Cassandra storage config YAML - -# NOTE: -# See http://wiki.apache.org/cassandra/StorageConfiguration for -# full explanations of configuration directives -# /NOTE - -# The name of the cluster. This is mainly used to prevent machines in -# one logical cluster from joining another. -cluster_name: 'Test Cluster' - -# You should always specify InitialToken when setting up a production -# cluster for the first time, and often when adding capacity later. -# The principle is that each node should be given an equal slice of -# the token ring; see http://wiki.apache.org/cassandra/Operations -# for more details. -# -# If blank, Cassandra will request a token bisecting the range of -# the heaviest-loaded existing node. If there is no load information -# available, such as is the case with a new cluster, it will pick -# a random token, which will lead to hot spots. -#initial_token: - -# See http://wiki.apache.org/cassandra/HintedHandoff -hinted_handoff_enabled: true -# this defines the maximum amount of time a dead host will have hints -# generated. After it has been dead this long, new hints for it will not be -# created until it has been seen alive and gone down again. -max_hint_window_in_ms: 10800000 # 3 hours -# Maximum throttle in KBs per second, per delivery thread. This will be -# reduced proportionally to the number of nodes in the cluster. (If there -# are two nodes in the cluster, each delivery thread will use the maximum -# rate; if there are three, each will throttle to half of the maximum, -# since we expect two nodes to be delivering hints simultaneously.) -hinted_handoff_throttle_in_kb: 1024 -# Number of threads with which to deliver hints; -# Consider increasing this number when you have multi-dc deployments, since -# cross-dc handoff tends to be slower -max_hints_delivery_threads: 2 - -# The following setting populates the page cache on memtable flush and compaction -# WARNING: Enable this setting only when the whole node's data fits in memory. -# Defaults to: false -# populate_io_cache_on_flush: false - -# Authentication backend, implementing IAuthenticator; used to identify users -# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, -# PasswordAuthenticator}. -# -# - AllowAllAuthenticator performs no checks - set it to disable authentication. -# - PasswordAuthenticator relies on username/password pairs to authenticate -# users. It keeps usernames and hashed passwords in system_auth.credentials table. -# Please increase system_auth keyspace replication factor if you use this authenticator. -authenticator: AllowAllAuthenticator - -# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions -# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, -# CassandraAuthorizer}. -# -# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. -# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please -# increase system_auth keyspace replication factor if you use this authorizer. -authorizer: AllowAllAuthorizer - -# Validity period for permissions cache (fetching permissions can be an -# expensive operation depending on the authorizer, CassandraAuthorizer is -# one example). Defaults to 2000, set to 0 to disable. -# Will be disabled automatically for AllowAllAuthorizer. -permissions_validity_in_ms: 2000 - - -# The partitioner is responsible for distributing rows (by key) across -# nodes in the cluster. Any IPartitioner may be used, including your -# own as long as it is on the classpath. Out of the box, Cassandra -# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner -# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}. -# -# - RandomPartitioner distributes rows across the cluster evenly by md5. -# This is the default prior to 1.2 and is retained for compatibility. -# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128 -# Hash Function instead of md5. When in doubt, this is the best option. -# - ByteOrderedPartitioner orders rows lexically by key bytes. BOP allows -# scanning rows in key order, but the ordering can generate hot spots -# for sequential insertion workloads. -# - OrderPreservingPartitioner is an obsolete form of BOP, that stores -# - keys in a less-efficient format and only works with keys that are -# UTF8-encoded Strings. -# - CollatingOPP collates according to EN,US rules rather than lexical byte -# ordering. Use this as an example if you need custom collation. -# -# See http://wiki.apache.org/cassandra/Operations for more on -# partitioners and token selection. -partitioner: org.apache.cassandra.dht.Murmur3Partitioner - -# directories where Cassandra should store data on disk. -data_file_directories: - - build/embeddedCassandra/data - -# commit log -commitlog_directory: build/embeddedCassandra/commitlog - -# policy for data disk failures: -# stop: shut down gossip and Thrift, leaving the node effectively dead, but -# can still be inspected via JMX. -# best_effort: stop using the failed disk and respond to requests based on -# remaining available sstables. This means you WILL see obsolete -# data at CL.ONE! -# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra -disk_failure_policy: stop - - -# Maximum size of the key cache in memory. -# -# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the -# minimum, sometimes more. The key cache is fairly tiny for the amount of -# time it saves, so it's worthwhile to use it at large numbers. -# The row cache saves even more time, but must store the whole values of -# its rows, so it is extremely space-intensive. It's best to only use the -# row cache if you have hot rows or static rows. -# -# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. -# -# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. -key_cache_size_in_mb: - -# Duration in seconds after which Cassandra should -# safe the keys cache. Caches are saved to saved_caches_directory as -# specified in this configuration file. -# -# Saved caches greatly improve cold-start speeds, and is relatively cheap in -# terms of I/O for the key cache. Row cache saving is much more expensive and -# has limited use. -# -# Default is 14400 or 4 hours. -key_cache_save_period: 14400 - -# Number of keys from the key cache to save -# Disabled by default, meaning all keys are going to be saved -# key_cache_keys_to_save: 100 - -# Maximum size of the row cache in memory. -# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. -# -# Default value is 0, to disable row caching. -row_cache_size_in_mb: 0 - -# Duration in seconds after which Cassandra should -# safe the row cache. Caches are saved to saved_caches_directory as specified -# in this configuration file. -# -# Saved caches greatly improve cold-start speeds, and is relatively cheap in -# terms of I/O for the key cache. Row cache saving is much more expensive and -# has limited use. -# -# Default is 0 to disable saving the row cache. -row_cache_save_period: 0 - -# Number of keys from the row cache to save -# Disabled by default, meaning all keys are going to be saved -# row_cache_keys_to_save: 100 - -# saved caches -saved_caches_directory: build/embeddedCassandra/saved_caches - -# commitlog_sync may be either "periodic" or "batch." -# When in batch mode, Cassandra won't ack writes until the commit log -# has been fsynced to disk. It will wait up to -# commitlog_sync_batch_window_in_ms milliseconds for other writes, before -# performing the sync. -# -# commitlog_sync: batch -# commitlog_sync_batch_window_in_ms: 50 -# -# the other option is "periodic" where writes may be acked immediately -# and the CommitLog is simply synced every commitlog_sync_period_in_ms -# milliseconds. -commitlog_sync: periodic -commitlog_sync_period_in_ms: 10000 - -# The size of the individual commitlog file segments. A commitlog -# segment may be archived, deleted, or recycled once all the data -# in it (potentially from each columnfamily in the system) has been -# flushed to sstables. -# -# The default size is 32, which is almost always fine, but if you are -# archiving commitlog segments (see commitlog_archiving.properties), -# then you probably want a finer granularity of archiving; 8 or 16 MB -# is reasonable. -commitlog_segment_size_in_mb: 32 - -# any class that implements the SeedProvider interface and has a -# constructor that takes a Map of parameters will do. -seed_provider: - # Addresses of hosts that are deemed contact points. - # Cassandra nodes use this list of hosts to find each other and learn - # the topology of the ring. You must change this if you are running - # multiple nodes! - - class_name: org.apache.cassandra.locator.SimpleSeedProvider - parameters: - # seeds is actually a comma-delimited list of addresses. - # Ex: ",," - - seeds: "127.0.0.1" - - -# For workloads with more data than can fit in memory, Cassandra's -# bottleneck will be reads that need to fetch data from -# disk. "concurrent_reads" should be set to (16 * number_of_drives) in -# order to allow the operations to enqueue low enough in the stack -# that the OS and drives can reorder them. -# -# On the other hand, since writes are almost never IO bound, the ideal -# number of "concurrent_writes" is dependent on the number of cores in -# your system; (8 * number_of_cores) is a good rule of thumb. -concurrent_reads: 32 -concurrent_writes: 32 - -# Total memory to use for memtables. Cassandra will flush the largest -# memtable when this much memory is used. -# If omitted, Cassandra will set it to 1/3 of the heap. -# memtable_total_space_in_mb: 2048 - -# Total space to use for commitlogs. -# If space gets above this value (it will round up to the next nearest -# segment multiple), Cassandra will flush every dirty CF in the oldest -# segment and remove it. -# commitlog_total_space_in_mb: 4096 - -# This sets the amount of memtable flush writer threads. These will -# be blocked by disk io, and each one will hold a memtable in memory -# while blocked. If you have a large heap and many data directories, -# you can increase this value for better flush performance. -# By default this will be set to the amount of data directories defined. -#memtable_flush_writers: 1 - -# the number of full memtables to allow pending flush, that is, -# waiting for a writer thread. At a minimum, this should be set to -# the maximum number of secondary indexes created on a single CF. -#memtable_flush_queue_size: 4 - -# Whether to, when doing sequential writing, fsync() at intervals in -# order to force the operating system to flush the dirty -# buffers. Enable this to avoid sudden dirty buffer flushing from -# impacting read latencies. Almost always a good idea on SSD:s; not -# necessarily on platters. -trickle_fsync: false -trickle_fsync_interval_in_kb: 10240 - -# TCP port, for commands and data -storage_port: 7010 - -# SSL port, for encrypted communication. Unused unless enabled in -# encryption_options -ssl_storage_port: 7011 - -# Address to bind to and tell other Cassandra nodes to connect to. You -# _must_ change this if you want multiple nodes to be able to -# communicate! -# -# Leaving it blank leaves it up to InetAddress.getLocalHost(). This -# will always do the Right Thing *if* the node is properly configured -# (hostname, name resolution, etc), and the Right Thing is to use the -# address associated with the hostname (it might not be). -# -# Setting this to 0.0.0.0 is always wrong. -listen_address: 127.0.0.1 - -start_native_transport: true -# port for the CQL native transport to listen for clients on -native_transport_port: 9142 - -# Whether to start the thrift rpc server. -start_rpc: true - -# Address to broadcast to other Cassandra nodes -# Leaving this blank will set it to the same value as listen_address -# broadcast_address: 1.2.3.4 - -# The address to bind the Thrift RPC service to -- clients connect -# here. Unlike ListenAddress above, you *can* specify 0.0.0.0 here if -# you want Thrift to listen on all interfaces. -# -# Leaving this blank has the same effect it does for ListenAddress, -# (i.e. it will be based on the configured hostname of the node). -rpc_address: localhost -# port for Thrift to listen for clients on -rpc_port: 9171 - -# enable or disable keepalive on rpc connections -rpc_keepalive: true - -# Cassandra provides three options for the RPC Server: -# -# sync -> One connection per thread in the rpc pool (see below). -# For a very large number of clients, memory will be your limiting -# factor; on a 64 bit JVM, 128KB is the minimum stack size per thread. -# Connection pooling is very, very strongly recommended. -# -# async -> Nonblocking server implementation with one thread to serve -# rpc connections. This is not recommended for high throughput use -# cases. Async has been tested to be about 50% slower than sync -# or hsha and is deprecated: it will be removed in the next major release. -# -# hsha -> Stands for "half synchronous, half asynchronous." The rpc thread pool -# (see below) is used to manage requests, but the threads are multiplexed -# across the different clients. -# -# The default is sync because on Windows hsha is about 30% slower. On Linux, -# sync/hsha performance is about the same, with hsha of course using less memory. -rpc_server_type: sync - -# Uncomment rpc_min|max|thread to set request pool size. -# You would primarily set max for the sync server to safeguard against -# misbehaved clients; if you do hit the max, Cassandra will block until one -# disconnects before accepting more. The defaults for sync are min of 16 and max -# unlimited. -# -# For the Hsha server, the min and max both default to quadruple the number of -# CPU cores. -# -# This configuration is ignored by the async server. -# -# rpc_min_threads: 16 -# rpc_max_threads: 2048 - -# uncomment to set socket buffer sizes on rpc connections -# rpc_send_buff_size_in_bytes: -# rpc_recv_buff_size_in_bytes: - -# Frame size for thrift (maximum field length). -# 0 disables TFramedTransport in favor of TSocket. This option -# is deprecated; we strongly recommend using Framed mode. -thrift_framed_transport_size_in_mb: 15 - -# The max length of a thrift message, including all fields and -# internal thrift overhead. -thrift_max_message_length_in_mb: 16 - -# Set to true to have Cassandra create a hard link to each sstable -# flushed or streamed locally in a backups/ subdirectory of the -# Keyspace data. Removing these links is the operator's -# responsibility. -incremental_backups: false - -# Whether or not to take a snapshot before each compaction. Be -# careful using this option, since Cassandra won't clean up the -# snapshots for you. Mostly useful if you're paranoid when there -# is a data format change. -snapshot_before_compaction: false - -# Whether or not a snapshot is taken of the data before keyspace truncation -# or dropping of column families. The STRONGLY advised default of true -# should be used to provide data safety. If you set this flag to false, you will -# lose data on truncation or drop. -auto_snapshot: false - -# Add column indexes to a row after its contents reach this size. -# Increase if your column values are large, or if you have a very large -# number of columns. The competing causes are, Cassandra has to -# deserialize this much of the row to read a single column, so you want -# it to be small - at least if you do many partial-row reads - but all -# the index data is read for each access, so you don't want to generate -# that wastefully either. -column_index_size_in_kb: 64 - -# Size limit for rows being compacted in memory. Larger rows will spill -# over to disk and use a slower two-pass compaction process. A message -# will be logged specifying the row key. -#in_memory_compaction_limit_in_mb: 64 - -# Number of simultaneous compactions to allow, NOT including -# validation "compactions" for anti-entropy repair. Simultaneous -# compactions can help preserve read performance in a mixed read/write -# workload, by mitigating the tendency of small sstables to accumulate -# during a single long running compactions. The default is usually -# fine and if you experience problems with compaction running too -# slowly or too fast, you should look at -# compaction_throughput_mb_per_sec first. -# -# This setting has no effect on LeveledCompactionStrategy. -# -# concurrent_compactors defaults to the number of cores. -# Uncomment to make compaction mono-threaded, the pre-0.8 default. -#concurrent_compactors: 1 - -# Multi-threaded compaction. When enabled, each compaction will use -# up to one thread per core, plus one thread per sstable being merged. -# This is usually only useful for SSD-based hardware: otherwise, -# your concern is usually to get compaction to do LESS i/o (see: -# compaction_throughput_mb_per_sec), not more. -#multithreaded_compaction: false - -# Throttles compaction to the given total throughput across the entire -# system. The faster you insert data, the faster you need to compact in -# order to keep the sstable count down, but in general, setting this to -# 16 to 32 times the rate you are inserting data is more than sufficient. -# Setting this to 0 disables throttling. Note that this account for all types -# of compaction, including validation compaction. -compaction_throughput_mb_per_sec: 16 - -# Track cached row keys during compaction, and re-cache their new -# positions in the compacted sstable. Disable if you use really large -# key caches. -#compaction_preheat_key_cache: true - -# Throttles all outbound streaming file transfers on this node to the -# given total throughput in Mbps. This is necessary because Cassandra does -# mostly sequential IO when streaming data during bootstrap or repair, which -# can lead to saturating the network connection and degrading rpc performance. -# When unset, the default is 200 Mbps or 25 MB/s. -# stream_throughput_outbound_megabits_per_sec: 200 - -# How long the coordinator should wait for read operations to complete -read_request_timeout_in_ms: 5000 -# How long the coordinator should wait for seq or index scans to complete -range_request_timeout_in_ms: 10000 -# How long the coordinator should wait for writes to complete -write_request_timeout_in_ms: 2000 -# How long a coordinator should continue to retry a CAS operation -# that contends with other proposals for the same row -cas_contention_timeout_in_ms: 1000 -# How long the coordinator should wait for truncates to complete -# (This can be much longer, because unless auto_snapshot is disabled -# we need to flush first so we can snapshot before removing the data.) -truncate_request_timeout_in_ms: 60000 -# The default timeout for other, miscellaneous operations -request_timeout_in_ms: 10000 - -# Enable operation timeout information exchange between nodes to accurately -# measure request timeouts. If disabled, replicas will assume that requests -# were forwarded to them instantly by the coordinator, which means that -# under overload conditions we will waste that much extra time processing -# already-timed-out requests. -# -# Warning: before enabling this property make sure to ntp is installed -# and the times are synchronized between the nodes. -cross_node_timeout: false - -# Enable socket timeout for streaming operation. -# When a timeout occurs during streaming, streaming is retried from the start -# of the current file. This _can_ involve re-streaming an important amount of -# data, so you should avoid setting the value too low. -# Default value is 0, which never timeout streams. -# streaming_socket_timeout_in_ms: 0 - -# phi value that must be reached for a host to be marked down. -# most users should never need to adjust this. -# phi_convict_threshold: 8 - -# endpoint_snitch -- Set this to a class that implements -# IEndpointSnitch. The snitch has two functions: -# - it teaches Cassandra enough about your network topology to route -# requests efficiently -# - it allows Cassandra to spread replicas around your cluster to avoid -# correlated failures. It does this by grouping machines into -# "datacenters" and "racks." Cassandra will do its best not to have -# more than one replica on the same "rack" (which may not actually -# be a physical location) -# -# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER, -# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS -# ARE PLACED. -# -# Out of the box, Cassandra provides -# - SimpleSnitch: -# Treats Strategy order as proximity. This improves cache locality -# when disabling read repair, which can further improve throughput. -# Only appropriate for single-datacenter deployments. -# - PropertyFileSnitch: -# Proximity is determined by rack and data center, which are -# explicitly configured in cassandra-topology.properties. -# - RackInferringSnitch: -# Proximity is determined by rack and data center, which are -# assumed to correspond to the 3rd and 2nd octet of each node's -# IP address, respectively. Unless this happens to match your -# deployment conventions (as it did Facebook's), this is best used -# as an example of writing a custom Snitch class. -# - Ec2Snitch: -# Appropriate for EC2 deployments in a single Region. Loads Region -# and Availability Zone information from the EC2 API. The Region is -# treated as the Datacenter, and the Availability Zone as the rack. -# Only private IPs are used, so this will not work across multiple -# Regions. -# - Ec2MultiRegionSnitch: -# Uses public IPs as broadcast_address to allow cross-region -# connectivity. (Thus, you should set seed addresses to the public -# IP as well.) You will need to open the storage_port or -# ssl_storage_port on the public IP firewall. (For intra-Region -# traffic, Cassandra will switch to the private IP after -# establishing a connection.) -# -# You can use a custom Snitch by setting this to the full class name -# of the snitch, which will be assumed to be on your classpath. -endpoint_snitch: SimpleSnitch - -# controls how often to perform the more expensive part of host score -# calculation -dynamic_snitch_update_interval_in_ms: 100 -# controls how often to reset all host scores, allowing a bad host to -# possibly recover -dynamic_snitch_reset_interval_in_ms: 600000 -# if set greater than zero and read_repair_chance is < 1.0, this will allow -# 'pinning' of replicas to hosts in order to increase cache capacity. -# The badness threshold will control how much worse the pinned host has to be -# before the dynamic snitch will prefer other replicas over it. This is -# expressed as a double which represents a percentage. Thus, a value of -# 0.2 means Cassandra would continue to prefer the static snitch values -# until the pinned host was 20% worse than the fastest. -dynamic_snitch_badness_threshold: 0.1 - -# request_scheduler -- Set this to a class that implements -# RequestScheduler, which will schedule incoming client requests -# according to the specific policy. This is useful for multi-tenancy -# with a single Cassandra cluster. -# NOTE: This is specifically for requests from the client and does -# not affect inter node communication. -# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place -# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of -# client requests to a node with a separate queue for each -# request_scheduler_id. The scheduler is further customized by -# request_scheduler_options as described below. -request_scheduler: org.apache.cassandra.scheduler.NoScheduler - -# Scheduler Options vary based on the type of scheduler -# NoScheduler - Has no options -# RoundRobin -# - throttle_limit -- The throttle_limit is the number of in-flight -# requests per client. Requests beyond -# that limit are queued up until -# running requests can complete. -# The value of 80 here is twice the number of -# concurrent_reads + concurrent_writes. -# - default_weight -- default_weight is optional and allows for -# overriding the default which is 1. -# - weights -- Weights are optional and will default to 1 or the -# overridden default_weight. The weight translates into how -# many requests are handled during each turn of the -# RoundRobin, based on the scheduler id. -# -# request_scheduler_options: -# throttle_limit: 80 -# default_weight: 5 -# weights: -# Keyspace1: 1 -# Keyspace2: 5 - -# request_scheduler_id -- An identifer based on which to perform -# the request scheduling. Currently the only valid option is keyspace. -# request_scheduler_id: keyspace - -# index_interval controls the sampling of entries from the primrary -# row index in terms of space versus time. The larger the interval, -# the smaller and less effective the sampling will be. In technicial -# terms, the interval coresponds to the number of index entries that -# are skipped between taking each sample. All the sampled entries -# must fit in memory. Generally, a value between 128 and 512 here -# coupled with a large key cache size on CFs results in the best trade -# offs. This value is not often changed, however if you have many -# very small rows (many to an OS page), then increasing this will -# often lower memory usage without a impact on performance. -index_interval: 128 - -# Enable or disable inter-node encryption -# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that -# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher -# suite for authentication, key exchange and encryption of the actual data transfers. -# NOTE: No custom encryption options are enabled at the moment -# The available internode options are : all, none, dc, rack -# -# If set to dc cassandra will encrypt the traffic between the DCs -# If set to rack cassandra will encrypt the traffic between the racks -# -# The passwords used in these options must match the passwords used when generating -# the keystore and truststore. For instructions on generating these files, see: -# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore -# -encryption_options: - internode_encryption: none - keystore: conf/.keystore - keystore_password: cassandra - truststore: conf/.truststore - truststore_password: cassandra - # More advanced defaults below: - # protocol: TLS - # algorithm: SunX509 - # store_type: JKS - # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA] \ No newline at end of file diff --git a/projects/copper-performance-test/PERFORMANCE_TEST_HOWTO.MD b/projects/copper-performance-test/PERFORMANCE_TEST_HOWTO.MD deleted file mode 100644 index dfb4e326..00000000 --- a/projects/copper-performance-test/PERFORMANCE_TEST_HOWTO.MD +++ /dev/null @@ -1,26 +0,0 @@ -How to run the COPPER performance test -====================================== - -1. Checkout COPPER - - git clone https://github.com/copper-engine/copper-engine.git copper - cd copper - -2. Build the performance test jarfile - - gradlew :projects:copper-performance-test:jar - -3. Prepare the database - * Oracle, MySQL, Postgres: create the database, user and schema - see https://github.com/copper-engine/copper-engine/tree/master/projects/copper-coreengine/src/main/database for the correspnding SQL files - * H2, DerbyDB: The database schema is created by COPPER performance test during startup. - * Apache Cassandra: create a new keyspace, or use an existing one. The database schema is created by COPPER performance test during startup. - -4. See usage of the performance test - - cd projects/copper-performance-test/build/libs/ - java -jar copper-performance-test.jar - -5. start the test as described in the usage, e.g. to start the latency performance test using an embedded H2 database: - - java -Dds.jdbcURL="jdbc:h2:mem:copperPerfTestH2DB;MVCC=TRUE" -Dds.driverClass=org.h2.Driver -jar copper-performance-test.jar latency - \ No newline at end of file diff --git a/projects/copper-performance-test/src/main/java/module-info.java b/projects/copper-performance-test/src/main/java/module-info.java deleted file mode 100644 index 4ec91e2a..00000000 --- a/projects/copper-performance-test/src/main/java/module-info.java +++ /dev/null @@ -1,11 +0,0 @@ -module org.copperengine.performancetest { - requires org.copperengine.core; - requires org.copperengine.ext; - requires org.copperengine.cassandra.storage; - - requires java.naming; - requires java.sql; - - requires org.slf4j; - requires c3p0; -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/impl/MockAdapter.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/impl/MockAdapter.java deleted file mode 100644 index 760139e6..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/impl/MockAdapter.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.impl; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.copperengine.core.Acknowledge; -import org.copperengine.core.Callback; -import org.copperengine.core.ProcessingEngine; -import org.copperengine.core.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MockAdapter { - - private static final Logger logger = LoggerFactory.getLogger(MockAdapter.class); - - private final int numberOfThreads; - private ScheduledExecutorService pool; - private int delay = 100; - private ProcessingEngine engine; - private AtomicInteger invokationCounter = new AtomicInteger(0); - private static final Acknowledge bestEffortAck = new Acknowledge.BestEffortAcknowledge(); - - public MockAdapter(int numberOfThreads) { - this.numberOfThreads = numberOfThreads; - } - - public void setEngine(ProcessingEngine engine) { - this.engine = engine; - } - - public void setDelayMSec(int delay) { - this.delay = delay; - } - - // do some work; delayed response to callback object - public void foo(final String param, final Callback cb) { - invokationCounter.incrementAndGet(); - if (delay <= 0) { - cb.notify(param, bestEffortAck); - } else { - pool.schedule(new Runnable() { - @Override - public void run() { - cb.notify(param, bestEffortAck); - } - }, delay, TimeUnit.MILLISECONDS); - } - } - - // do some work; delayed response to engine object - public void foo(final String param, final String cid) { - foo(param, cid, delay); - } - - // do some work; delayed response to engine object - public void foo(final String param, final String cid, int overrideDelay) { - invokationCounter.incrementAndGet(); - if (overrideDelay <= 0) { - engine.notify(new Response(cid, param, null), bestEffortAck); - } else { - pool.schedule(new Runnable() { - @Override - public void run() { - engine.notify(new Response(cid, param, null), bestEffortAck); - } - }, overrideDelay, TimeUnit.MILLISECONDS); - } - } - - // do some work; delayed response to engine object - public void fooWithMultiResponse(final String param, final String cid, final int numbOfResponse) { - invokationCounter.incrementAndGet(); - pool.schedule(new Runnable() { - @Override - public void run() { - for (int i = 0; i < numbOfResponse; i++) { - engine.notify(new Response(cid, param, null), bestEffortAck); - } - } - }, delay, TimeUnit.MILLISECONDS); - } - - // do some work; delayed resonse to engine object - public void incrementAsync(final int c, final String cid) { - invokationCounter.incrementAndGet(); - if (delay <= 0) { - engine.notify(new Response(cid, c + 1, null), bestEffortAck); - } else { - pool.schedule(new Runnable() { - @Override - public void run() { - engine.notify(new Response(cid, c + 1, null), bestEffortAck); - } - }, delay, TimeUnit.MILLISECONDS); - } - } - - // do some work; t once resonse to engine object - public void incrementSync(final int c, final String cid) { - invokationCounter.incrementAndGet(); - engine.notify(new Response(cid, c + 1, null), bestEffortAck); - } - - public synchronized void shutdown() { - if (pool != null) { - logger.debug("Shutting down..."); - pool.shutdown(); - pool = null; - } - } - - public int getInvokationCounter() { - return invokationCounter.get(); - } - - public synchronized void startup() { - if (pool == null) { - logger.debug("Starting up..."); - pool = Executors.newScheduledThreadPool(numberOfThreads); - } - } - - // generate and return the correlation id - public String foo(String param) { - final String cid = engine.createUUID(); - this.foo(param, cid); - return cid; - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameter.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameter.java deleted file mode 100644 index 7027e531..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameter.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.util.ArrayList; -import java.util.List; - -import org.copperengine.core.persistent.PersistentPriorityProcessorPool; -import org.copperengine.core.persistent.StandardJavaSerializer; - -public enum ConfigParameter { - - // common configuration parameters - PROC_POOL_NUMB_OF_THREADS("procPool.numberOfThreads", "Number of processor threads per processor pool", Integer.valueOf(Runtime.getRuntime().availableProcessors()), ConfigParameterGroup.common), - PROC_DEQUEUE_BULK_SIZE("procPool.dequeueBulkSize", "Max. bulk size when fetching workflow instances from the underlying DB", Integer.valueOf(PersistentPriorityProcessorPool.DEFAULT_DEQUEUE_SIZE), ConfigParameterGroup.common), - MOCK_ADAPTER_NUMB_OF_THREADS("mockAdapter.numberOfThreads", "Number of processor threads in adapter mock", Integer.valueOf(Runtime.getRuntime().availableProcessors()), ConfigParameterGroup.common), - COMPRESSION("compression", "compress workflow instances in DB?", StandardJavaSerializer.DEFAULT_COMPRESS, ConfigParameterGroup.common), - - // configuration parameters used only for RDBMS, e.g. Oracle - DS_JDBC_URL("ds.jdbcURL", "jdbc URL", null, ConfigParameterGroup.rdbms, "mandatory when testing RDBMS"), - DS_DRIVER_CLASS("ds.driverClass", "jdbc driver class", null, ConfigParameterGroup.rdbms), - DS_USER("ds.user", "jdbc user", null, ConfigParameterGroup.rdbms), - DS_PASSWORD("ds.password", "jdbc password", null, ConfigParameterGroup.rdbms), - DS_MIN_POOL_SIZE("ds.minPoolSize", "minimum size of the connection pool", Integer.valueOf(Runtime.getRuntime().availableProcessors()), ConfigParameterGroup.rdbms), - DS_MAX_POOL_SIZE("ds.maxPoolSize", "maximum size of the connection pool", Integer.valueOf(Runtime.getRuntime().availableProcessors() * 2), ConfigParameterGroup.rdbms), - BATCHER_NUMB_OF_THREADS("batcher.numberOfThreads", "Number of DB batcher threads", Integer.valueOf(Runtime.getRuntime().availableProcessors()), ConfigParameterGroup.rdbms), - - // configuration parameters used only for Cassandra DB - CASSANDRA_HOSTS("cassandra.hosts", "comma separated list of initial cassandra nodes", null, ConfigParameterGroup.cassandra, "mandatory when testing with Cassandra DB"), - CASSANDRA_PORT("cassandra.port", "cassandra port", com.datastax.driver.core.ProtocolOptions.DEFAULT_PORT, ConfigParameterGroup.cassandra), - CASSANDRA_KEYSPACE("cassandra.keyspace", "cassandra keyspace", "copper", ConfigParameterGroup.cassandra), - - // configuration parameters used only in the throughput performance test - THROUGHPUTTEST_NUMBER_OF_WORKFLOW_INSTANCES("throughput.numberOfWfI", "Number of workflow instances to process in the test", 20000, ConfigParameterGroup.throughput), - THROUGHPUTTEST_DATA_SIZE("throughput.dataSize", "Size of the data argument passed to the workflow instances", 50, ConfigParameterGroup.throughput), - THROUGHPUTTEST_NUMBER_OF_INSERT_THREADS("throughput.numberOfInsertThreads", "Number of concurrent insert threads", 1, ConfigParameterGroup.throughput), - THROUGHPUTTEST_BATCHS_SIZE("throughput.batchSize", "insert batch size", 100, ConfigParameterGroup.throughput), - THROUGHPUTTEST_NUMBER_OF_EXTRA_PROC_POOLS("throughput.numberOfExtraProcPools", "number of extra processor pools", 0, ConfigParameterGroup.throughput), - - // configuration parameters used only in the latency performance test - LATENCY_NUMBER_OF_WORKFLOW_INSTANCES("latency.numberOfWfI", "Number of workflow instances to process in the test", 50, ConfigParameterGroup.latency), - LATENCY_DATA_SIZE("latency.dataSize", "Size of the data argument passed to the workflow instances", 1000, ConfigParameterGroup.latency); - - private ConfigParameter(String key, String description, Object defaultValue, ConfigParameterGroup grp) { - this.key = key; - this.description = description; - this.grp = grp; - this.mandatory = "optional"; - this.defaultValue = defaultValue; - } - - private ConfigParameter(String key, String description, Object defaultValue, ConfigParameterGroup grp, String mandatory) { - this.key = key; - this.description = description; - this.grp = grp; - this.mandatory = mandatory; - this.defaultValue = defaultValue; - } - - private final String key; - private final String description; - private final ConfigParameterGroup grp; - private final String mandatory; - private final Object defaultValue; - - public String getDescription() { - return description; - } - - public ConfigParameterGroup getGrp() { - return grp; - } - - public String getKey() { - return key; - } - - public static List all4group(ConfigParameterGroup grp) { - List rv = new ArrayList<>(); - for (ConfigParameter x : ConfigParameter.values()) { - if (x.grp == grp) { - rv.add(x); - } - } - return rv; - } - - public String getMandatory() { - return mandatory; - } - - public Object getDefaultValue() { - return defaultValue; - } - -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameterGroup.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameterGroup.java deleted file mode 100644 index 9661ab28..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigParameterGroup.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -public enum ConfigParameterGroup { - common("common configuration parameters"), - rdbms("configuration parameters used only for RDBMS, e.g. Oracle, MySQL"), - cassandra("configuration parameters used only for Apache Cassandra DB"), - latency("configuration parameters used only in the latency performance test"), - throughput("configuration parameters used only in the throughput performance test"); - - private final String description; - - private ConfigParameterGroup(final String description) { - this.description = description; - } - - public String getDescription() { - return description; - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigurationManager.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigurationManager.java deleted file mode 100644 index 0697c9cd..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ConfigurationManager.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.io.PrintStream; -import java.util.Properties; - -import org.slf4j.Logger; - -public class ConfigurationManager { - - private final Properties props; - - public ConfigurationManager(Properties props) { - this.props = props; - } - - public int getConfigInt(ConfigParameter p) { - String v = props.getProperty(p.getKey()); - if (v == null || v.trim().isEmpty()) - return ((Integer) p.getDefaultValue()).intValue(); - return Integer.parseInt(v); - } - - public boolean getConfigBoolean(ConfigParameter p) { - String v = props.getProperty(p.getKey()); - if (v == null || v.trim().isEmpty()) - return ((Boolean) p.getDefaultValue()).booleanValue(); - return Boolean.parseBoolean(v); - } - - public Integer getConfigInteger(ConfigParameter p) { - String v = props.getProperty(p.getKey()); - if (v == null || v.trim().isEmpty()) - return (Integer) p.getDefaultValue(); - return Integer.parseInt(v); - } - - public String getConfigString(ConfigParameter p) { - String v = props.getProperty(p.getKey()); - if (v == null || v.trim().isEmpty()) - return (String) p.getDefaultValue(); - return v; - } - - private Object getConfig(ConfigParameter p) { - Object v = props.getProperty(p.getKey()); - return v == null ? p.getDefaultValue() : v; - } - - public void print(PrintStream ps) { - for (ConfigParameterGroup grp : ConfigParameterGroup.values()) { - for (ConfigParameter p : ConfigParameter.all4group(grp)) { - System.out.println(p.getKey() + "=" + getConfig(p)); - } - } - } - - public void log(Logger logger, ConfigParameterGroup... grps) { - logger.info("Configuration parameters:"); - for (ConfigParameterGroup grp : grps) { - for (ConfigParameter p : ConfigParameter.all4group(grp)) { - logger.info("{}={}", p.getKey(), getConfig(p)); - } - } - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/DataSourceFactory.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/DataSourceFactory.java deleted file mode 100644 index 69982c17..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/DataSourceFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.util.Properties; - -import com.mchange.v2.c3p0.ComboPooledDataSource; - -public class DataSourceFactory { - - public static ComboPooledDataSource createDataSource(Properties props) { - try { - final String jdbcUrl = trim(props.getProperty(ConfigParameter.DS_JDBC_URL.getKey())); - final String user = trim(props.getProperty(ConfigParameter.DS_USER.getKey())); - final String password = trim(props.getProperty(ConfigParameter.DS_PASSWORD.getKey())); - final String driverClass = trim(props.getProperty(ConfigParameter.DS_DRIVER_CLASS.getKey())); - final int minPoolSize = Integer.valueOf(props.getProperty(ConfigParameter.DS_MIN_POOL_SIZE.getKey(), Integer.toString(Runtime.getRuntime().availableProcessors()))); - final int maxPoolSize = Integer.valueOf(props.getProperty(ConfigParameter.DS_MAX_POOL_SIZE.getKey(), Integer.toString(2 * Runtime.getRuntime().availableProcessors()))); - ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl.replace("${NOW}", Long.toString(System.currentTimeMillis()))); - if (!isNullOrEmpty(user)) - ds.setUser(user); - if (!isNullOrEmpty(password)) - ds.setPassword(password); - if (!isNullOrEmpty(driverClass)) - ds.setDriverClass(driverClass); - ds.setMinPoolSize(minPoolSize); - ds.setInitialPoolSize(minPoolSize); - ds.setMaxPoolSize(maxPoolSize); - return ds; - } catch (Exception e) { - throw new RuntimeException("Unable to create datasource", e); - } - } - - private static boolean isNullOrEmpty(String s) { - return s == null || s.isEmpty(); - } - - private static String trim(String s) { - return s == null ? null : s.trim(); - } - -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/LatencyPerformanceTest.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/LatencyPerformanceTest.java deleted file mode 100644 index f73e0e39..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/LatencyPerformanceTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.util.Random; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.PersistentProcessingEngine; -import org.copperengine.core.WorkflowInstanceDescr; -import org.copperengine.management.model.MeasurePointData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class LatencyPerformanceTest { - - private static final Logger logger = LoggerFactory.getLogger(LatencyPerformanceTest.class); - - protected String createTestData(int size) { - StringBuilder sb = new StringBuilder(size); - Random r = new Random(); - for (int i = 0; i < size; i++) { - sb.append(r.nextInt(2) == 0 ? "0" : "1"); - } - return sb.toString(); - } - - public void run() { - try (PerformanceTestContext context = new PerformanceTestContext()) { - final int dataSize = context.getConfigManager().getConfigInt(ConfigParameter.LATENCY_DATA_SIZE); - final int numbOfWfI = context.getConfigManager().getConfigInt(ConfigParameter.LATENCY_NUMBER_OF_WORKFLOW_INSTANCES); - final String data = createTestData(dataSize); - final PersistentProcessingEngine engine = context.getEngine(); - final Random random = new Random(); - - context.getConfigManager().log(logger, ConfigParameterGroup.latency, ConfigParameterGroup.common, context.isCassandraTest() ? ConfigParameterGroup.cassandra : ConfigParameterGroup.rdbms); - logger.info("Starting latency performance test with {} workflow instances and data size {} chars ...", numbOfWfI, dataSize); - final long startTS = System.currentTimeMillis(); - for (int i = 0; i < numbOfWfI; i++) { - String wfiId = engine.run(new WorkflowInstanceDescr<>("org.copperengine.performancetest.workflows.SavepointPerfTestWorkflow", data)); - context.getBackchannel().wait(wfiId, 1, TimeUnit.MINUTES); - Thread.sleep(random.nextInt(100) + 5); - } - final long et = System.currentTimeMillis() - startTS; - final MeasurePointData mp = context.getStatisticsCollector().query("savepoint.latency"); - final double avgLatency = (mp.getElapsedTimeMicros() / mp.getCount()) / 1000.0; - logger.info("Finished performance test with {} workflow instances in {} msec, avg latency is {} msec", numbOfWfI, et, avgLatency); - - Thread.sleep(5000); // drain the batcher, etc. - logger.info("statistics:\n{}", context.getStatisticsCollector().print()); - - } catch (Exception e) { - logger.error("performance test failed", e); - } - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/Main.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/Main.java deleted file mode 100644 index 37e5838e..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/Main.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public class Main { - - public static void main(String[] args) { - System.setProperty("ts", new SimpleDateFormat("yyyyMMdd_HHmmSS").format(new Date())); - try { - if (args.length == 0) { - usage(); - } - else if ("latency".equalsIgnoreCase(args[0])) { - new LatencyPerformanceTest().run(); - } - else if ("throughput".equalsIgnoreCase(args[0])) { - new ThroughputPerformanceTest().run(); - } - else { - usage(); - } - } catch (Exception e) { - e.printStackTrace(); - usage(); - } - System.exit(0); - } - - private static void usage() { - System.out.println("Usage: java -jar copper-performance-test.jar (latency|throughput)"); - System.out.println(" latency - measures the latency for executing resubmit/savepoints in an otherwise idle system"); - System.out.println(" throughput - executes a large amount of workflow instances, each with 10 wait/notifies, to measure the avg. number of wait/notify cycles per second"); - System.out.println(); - System.out.println(" with as follows"); - for (ConfigParameterGroup grp : ConfigParameterGroup.values()) { - System.out.println(" ** " + grp.getDescription() + " **"); - for (ConfigParameter p : ConfigParameter.all4group(grp)) { - System.out.println(" -D" + p.getKey() + "= --> (" + p.getMandatory() + ") " + p.getDescription() + (p.getDefaultValue() != null ? (" - default value is " + p.getDefaultValue()) : "")); - } - System.out.println(); - } - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/PerformanceTestContext.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/PerformanceTestContext.java deleted file mode 100644 index 338ffb5e..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/PerformanceTestContext.java +++ /dev/null @@ -1,498 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.io.InputStream; -import java.sql.Connection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import javax.sql.DataSource; - -import org.copperengine.core.AbstractDependencyInjector; -import org.copperengine.core.CopperRuntimeException; -import org.copperengine.core.DependencyInjector; -import org.copperengine.core.EngineIdProvider; -import org.copperengine.core.EngineIdProviderBean; -import org.copperengine.core.PersistentProcessingEngine; -import org.copperengine.core.batcher.RetryingTxnBatchRunner; -import org.copperengine.core.batcher.impl.BatcherImpl; -import org.copperengine.core.common.DefaultProcessorPoolManager; -import org.copperengine.core.common.JdkRandomUUIDFactory; -import org.copperengine.core.common.ProcessorPoolManager; -import org.copperengine.core.common.WorkflowRepository; -import org.copperengine.core.monitoring.LoggingStatisticCollector; -import org.copperengine.core.monitoring.RuntimeStatisticsCollector; -import org.copperengine.core.persistent.DatabaseDialect; -import org.copperengine.core.persistent.DerbyDbDialect; -import org.copperengine.core.persistent.H2Dialect; -import org.copperengine.core.persistent.MySqlDialect; -import org.copperengine.core.persistent.OracleDialect; -import org.copperengine.core.persistent.OracleSimpleDialect; -import org.copperengine.core.persistent.PersistentPriorityProcessorPool; -import org.copperengine.core.persistent.PersistentProcessorPool; -import org.copperengine.core.persistent.PersistentScottyEngine; -import org.copperengine.core.persistent.PostgreSQLDialect; -import org.copperengine.core.persistent.ScottyDBStorage; -import org.copperengine.core.persistent.ScottyDBStorageInterface; -import org.copperengine.core.persistent.Serializer; -import org.copperengine.core.persistent.StandardJavaSerializer; -import org.copperengine.core.persistent.cassandra.CassandraSessionManagerImpl; -import org.copperengine.core.persistent.cassandra.CassandraStorage; -import org.copperengine.core.persistent.hybrid.DefaultTimeoutManager; -import org.copperengine.core.persistent.hybrid.HybridDBStorage; -import org.copperengine.core.persistent.hybrid.HybridTransactionController; -import org.copperengine.core.persistent.hybrid.StorageCache; -import org.copperengine.core.persistent.txn.CopperTransactionController; -import org.copperengine.core.persistent.txn.TransactionController; -import org.copperengine.core.util.Backchannel; -import org.copperengine.core.util.BackchannelDefaultImpl; -import org.copperengine.ext.wfrepo.classpath.ClasspathWorkflowRepository; -import org.copperengine.performancetest.impl.MockAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.mchange.v2.c3p0.ComboPooledDataSource; - -public class PerformanceTestContext implements AutoCloseable { - - private static final Logger logger = LoggerFactory.getLogger(PerformanceTestContext.class); - - protected final Map> suppliers = new HashMap<>(); - protected final Supplier props; - protected final Supplier mockAdapter; - protected final Supplier dependencyInjector; - protected final Supplier backchannel; - protected final Supplier engine; - protected final Supplier repo; - protected final Supplier statisticsCollector; - protected final Supplier engineIdProvider; - protected final Supplier serializer; - protected final Supplier> processorPoolManager; - protected final Supplier configManager; - protected TransactionController transactionController = null; - private final List shutdownHooks = new ArrayList<>(); - - public PerformanceTestContext() { - configManager = Suppliers.memoize(new Supplier() { - @Override - public ConfigurationManager get() { - return createConfigurationManager(); - } - }); - suppliers.put("configManager", configManager); - - processorPoolManager = Suppliers.memoize(new Supplier>() { - @Override - public ProcessorPoolManager get() { - return createProcessorPoolManager(); - } - }); - suppliers.put("processorPoolManager", processorPoolManager); - - serializer = Suppliers.memoize(new Supplier() { - @Override - public Serializer get() { - return createSerializer(); - } - }); - suppliers.put("serializer", serializer); - - engineIdProvider = Suppliers.memoize(new Supplier() { - @Override - public EngineIdProvider get() { - return createEngineIdProvider(); - } - }); - suppliers.put("engineIdProvider", engineIdProvider); - - statisticsCollector = Suppliers.memoize(new Supplier() { - @Override - public LoggingStatisticCollector get() { - return createStatisticsCollector(); - } - }); - suppliers.put("statisticsCollector", statisticsCollector); - - repo = Suppliers.memoize(new Supplier() { - @Override - public WorkflowRepository get() { - return createWorkflowRepository(); - } - }); - suppliers.put("repo", repo); - - engine = Suppliers.memoize(new Supplier() { - @Override - public PersistentProcessingEngine get() { - return createPersistentProcessingEngine(); - } - }); - suppliers.put("engine", engine); - - props = Suppliers.memoize(new Supplier() { - @Override - public Properties get() { - return createProperties(); - } - }); - suppliers.put("props", props); - - mockAdapter = Suppliers.memoize(new Supplier() { - @Override - public MockAdapter get() { - return createMockAdapter(); - } - }); - suppliers.put("mockAdapter", mockAdapter); - - backchannel = Suppliers.memoize(new Supplier() { - @Override - public Backchannel get() { - return createBackchannel(); - } - }); - suppliers.put("backchannel", backchannel); - - dependencyInjector = Suppliers.memoize(new Supplier() { - @Override - public DependencyInjector get() { - return createDependencyInjector(); - } - }); - suppliers.put("dependencyInjector", dependencyInjector); - - startup(); - } - - protected ConfigurationManager createConfigurationManager() { - return new ConfigurationManager(props.get()); - } - - protected ProcessorPoolManager createProcessorPoolManager() { - return new DefaultProcessorPoolManager(); - } - - protected Serializer createSerializer() { - StandardJavaSerializer serializer = new StandardJavaSerializer(); - boolean compression = configManager.get().getConfigBoolean(ConfigParameter.COMPRESSION); - logger.debug("compression={}", compression); - serializer.setCompress(compression); - return serializer; - } - - protected EngineIdProvider createEngineIdProvider() { - return new EngineIdProviderBean("perftest"); - } - - protected LoggingStatisticCollector createStatisticsCollector() { - LoggingStatisticCollector statCollector = new LoggingStatisticCollector(); - statCollector.setLoggingIntervalSec(10); - statCollector.setResetAfterLogging(false); - return statCollector; - } - - protected WorkflowRepository createWorkflowRepository() { - return new ClasspathWorkflowRepository("org.copperengine.performancetest.workflows"); - } - - protected DatabaseDialect createDialect(DataSource ds, WorkflowRepository wfRepository, EngineIdProvider engineIdProvider, RuntimeStatisticsCollector runtimeStatisticsCollector, Serializer serializer) { - try (Connection c = ds.getConnection()) { - String name = c.getMetaData().getDatabaseProductName(); - logger.info("Test database type is {}", name); - if ("oracle".equalsIgnoreCase(name)) { - if (OracleDialect.schemaMatches(c)) { - OracleDialect dialect = new OracleDialect(); - dialect.setWfRepository(wfRepository); - dialect.setEngineIdProvider(engineIdProvider); - dialect.setMultiEngineMode(false); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - dialect.startup(); - return dialect; - } - else { - OracleSimpleDialect dialect = new OracleSimpleDialect(); - dialect.setWfRepository(wfRepository); - dialect.setEngineIdProvider(engineIdProvider); - dialect.setMultiEngineMode(false); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - dialect.startup(); - return dialect; - } - } - if ("Apache Derby".equalsIgnoreCase(name)) { - DerbyDbDialect dialect = new DerbyDbDialect(); - dialect.setDataSource(ds); - dialect.setWfRepository(wfRepository); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - DerbyDbDialect.checkAndCreateSchema(ds); - return dialect; - } - if ("H2".equalsIgnoreCase(name)) { - H2Dialect dialect = new H2Dialect(); - dialect.setDataSource(ds); - dialect.setWfRepository(wfRepository); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - H2Dialect.checkAndCreateSchema(ds); - return dialect; - } - if ("MySQL".equalsIgnoreCase(name)) { - MySqlDialect dialect = new MySqlDialect(); - dialect.setWfRepository(wfRepository); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - return dialect; - } - if ("PostgreSQL".equalsIgnoreCase(name)) { - PostgreSQLDialect dialect = new PostgreSQLDialect(); - dialect.setWfRepository(wfRepository); - dialect.setRuntimeStatisticsCollector(runtimeStatisticsCollector); - dialect.setSerializer(serializer); - return dialect; - } - throw new Error("No dialect available for DBMS " + name); - } catch (Exception e) { - throw new CopperRuntimeException("Unable to create dialect", e); - } - } - - protected PersistentProcessingEngine createPersistentProcessingEngine() { - ScottyDBStorageInterface dbStorageInterface = null; - - if (!isCassandraTest()) { - final int batcherNumbOfThreads = configManager.get().getConfigInt(ConfigParameter.BATCHER_NUMB_OF_THREADS); - logger.debug("Starting batcher with {} worker threads", batcherNumbOfThreads); - - final ComboPooledDataSource dataSource = DataSourceFactory.createDataSource(props.get()); - transactionController = new CopperTransactionController(dataSource); - - final BatcherImpl batcher = new BatcherImpl(batcherNumbOfThreads); - batcher.setBatchRunner(new RetryingTxnBatchRunner<>(dataSource)); - batcher.setStatisticsCollector(statisticsCollector.get()); - batcher.startup(); - - ScottyDBStorage dbStorage = new ScottyDBStorage(); - dbStorage.setBatcher(batcher); - dbStorage.setCheckDbConsistencyAtStartup(false); - dbStorage.setDialect(createDialect(dataSource, repo.get(), engineIdProvider.get(), statisticsCollector.get(), serializer.get())); - dbStorage.setTransactionController(transactionController); - dbStorageInterface = dbStorage; - - shutdownHooks.add(new Runnable() { - @Override - public void run() { - batcher.shutdown(); - dataSource.close(); - } - }); - - } - else { - transactionController = new HybridTransactionController(); - - final String cassandraHosts = props.get().getProperty(ConfigParameter.CASSANDRA_HOSTS.getKey()); - final CassandraSessionManagerImpl sessionManager = new CassandraSessionManagerImpl(Arrays.asList(cassandraHosts.split(",")), configManager.get().getConfigInteger(ConfigParameter.CASSANDRA_PORT), configManager.get().getConfigString(ConfigParameter.CASSANDRA_KEYSPACE)); - sessionManager.startup(); - - final DefaultTimeoutManager timeoutManager = new DefaultTimeoutManager(); - timeoutManager.startup(); - - final ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - CassandraStorage storage = new CassandraStorage(sessionManager, pool, statisticsCollector.get()); - storage.setCreateSchemaOnStartup(true); - - HybridDBStorage dbStorage = new HybridDBStorage(serializer.get(), repo.get(), new StorageCache(storage), timeoutManager, pool); - dbStorageInterface = dbStorage; - - shutdownHooks.add(new Runnable() { - @Override - public void run() { - try { - timeoutManager.shutdown(); - sessionManager.shutdown(); - pool.shutdown(); - pool.awaitTermination(5, TimeUnit.SECONDS); - } - catch (Exception e) { - logger.error("shutdown failed", e); - } - } - }); - } - - final int procPoolNumbOfThreads = configManager.get().getConfigInt(ConfigParameter.PROC_POOL_NUMB_OF_THREADS); - logger.debug("Starting default processor pool with {} worker threads", procPoolNumbOfThreads); - final List pools = new ArrayList(); - final PersistentPriorityProcessorPool pool = new PersistentPriorityProcessorPool(PersistentProcessorPool.DEFAULT_POOL_ID, transactionController, procPoolNumbOfThreads); - pool.setDequeueBulkSize(configManager.get().getConfigInt(ConfigParameter.PROC_DEQUEUE_BULK_SIZE)); - pools.add(pool); - processorPoolManager.get().setProcessorPools(pools); - - PersistentScottyEngine engine = new PersistentScottyEngine(); - engine.setWfRepository(repo.get()); - engine.setStatisticsCollector(statisticsCollector.get()); - engine.setEngineIdProvider(engineIdProvider.get()); - engine.setIdFactory(new JdkRandomUUIDFactory()); - engine.setProcessorPoolManager(processorPoolManager.get()); - engine.setDbStorage(dbStorageInterface); - engine.setDependencyInjector(dependencyInjector.get()); - return engine; - } - - protected DependencyInjector createDependencyInjector() { - AbstractDependencyInjector dependencyInjector = new AbstractDependencyInjector() { - @Override - public String getType() { - return null; - } - - @Override - protected Object getBean(String beanId) { - Supplier supplier = suppliers.get(beanId); - if (supplier == null) { - throw new RuntimeException("No supplier with id '" + beanId + "' found!"); - } - else { - return supplier.get(); - } - } - }; - return dependencyInjector; - } - - protected Properties createProperties() { - try { - Properties defaults = new Properties(); - logger.debug("Loading properties from 'performancetest.default.properties'..."); - defaults.load(DataSourceFactory.class.getResourceAsStream("/performancetest.default.properties")); - - Properties specific = new Properties(); - String username = System.getProperty("user.name", "undefined"); - InputStream is = DataSourceFactory.class.getResourceAsStream("/performancetest." + username + ".properties"); - if (is != null) { - logger.info("Loading properties from 'performancetest." + username + ".properties'..."); - specific.load(is); - } - - Properties p = new Properties(); - p.putAll(defaults); - p.putAll(specific); - p.putAll(System.getProperties()); - - List keys = new ArrayList<>(); - for (Object key : p.keySet()) { - keys.add(key.toString()); - } - Collections.sort(keys); - for (String key : keys) { - logger.debug("Property {}='{}'", key, p.getProperty(key)); - } - return p; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException("failed to load properties", e); - } - } - - protected Backchannel createBackchannel() { - return new BackchannelDefaultImpl(); - } - - protected MockAdapter createMockAdapter() { - int numberOfThreads = configManager.get().getConfigInt(ConfigParameter.MOCK_ADAPTER_NUMB_OF_THREADS); - logger.debug("MockAdapter.numberOfThreads={}", numberOfThreads); - MockAdapter x = new MockAdapter(numberOfThreads); - x.setEngine(engine.get()); - return x; - } - - public PersistentProcessingEngine getEngine() { - return engine.get(); - } - - public void startup() { - for (Supplier s : suppliers.values()) { - s.get(); - } - mockAdapter.get().startup(); - statisticsCollector.get().start(); - engine.get().startup(); - }; - - public void shutdown() { - engine.get().shutdown(); - statisticsCollector.get().shutdown(); - mockAdapter.get().shutdown(); - for (Runnable r : shutdownHooks) { - r.run(); - } - } - - @Override - public void close() { - shutdown(); - } - - public void registerBean(final String id, final Object bean) { - suppliers.put(id, new Supplier() { - @Override - public Object get() { - return bean; - } - }); - } - - public LoggingStatisticCollector getStatisticsCollector() { - return statisticsCollector.get(); - } - - public Backchannel getBackchannel() { - return backchannel.get(); - } - - public ProcessorPoolManager getProcessorPoolManager() { - return processorPoolManager.get(); - } - - public TransactionController getTransactionController() { - return transactionController; - } - - public ConfigurationManager getConfigManager() { - return configManager.get(); - } - - public boolean isCassandraTest() { - final String cassandraHosts = props.get().getProperty(ConfigParameter.CASSANDRA_HOSTS.getKey()); - return cassandraHosts != null && !cassandraHosts.isEmpty(); - } - -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ThroughputPerformanceTest.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ThroughputPerformanceTest.java deleted file mode 100644 index 5014df7e..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/main/ThroughputPerformanceTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.main; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.PersistentProcessingEngine; -import org.copperengine.core.WorkflowInstanceDescr; -import org.copperengine.core.persistent.PersistentPriorityProcessorPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ThroughputPerformanceTest { - - private static final Logger logger = LoggerFactory.getLogger(ThroughputPerformanceTest.class); - - protected String createTestData(int size) { - StringBuilder sb = new StringBuilder(size); - Random r = new Random(); - for (int i = 0; i < size; i++) { - sb.append(r.nextInt(2) == 0 ? "0" : "1"); - } - return sb.toString(); - } - - public void run() { - try (PerformanceTestContext context = new PerformanceTestContext()) { - final int numberOfExtraProcessorPools = context.getConfigManager().getConfigInt(ConfigParameter.THROUGHPUTTEST_NUMBER_OF_EXTRA_PROC_POOLS); - final int insertThreads = context.getConfigManager().getConfigInt(ConfigParameter.THROUGHPUTTEST_NUMBER_OF_INSERT_THREADS); - final int insertBatchSize = context.getConfigManager().getConfigInt(ConfigParameter.THROUGHPUTTEST_BATCHS_SIZE); - final int dataSize = context.getConfigManager().getConfigInt(ConfigParameter.THROUGHPUTTEST_DATA_SIZE); - final int numbOfWfI = context.getConfigManager().getConfigInt(ConfigParameter.THROUGHPUTTEST_NUMBER_OF_WORKFLOW_INSTANCES); - final String data = createTestData(dataSize); - final PersistentProcessingEngine engine = context.getEngine(); - final Semaphore semaphore = new Semaphore(numbOfWfI); - context.registerBean("semaphore", semaphore); - - for (int i = 0; i < numberOfExtraProcessorPools; i++) { - final int procPoolNumbOfThreads = context.getConfigManager().getConfigInt(ConfigParameter.PROC_POOL_NUMB_OF_THREADS); - final String ppoolId = "P" + i; - logger.debug("Starting additional processor pool {} with {} threads", ppoolId, procPoolNumbOfThreads); - final PersistentPriorityProcessorPool pool = new PersistentPriorityProcessorPool(ppoolId, context.getTransactionController(), procPoolNumbOfThreads); - pool.setDequeueBulkSize(context.getConfigManager().getConfigInt(ConfigParameter.PROC_DEQUEUE_BULK_SIZE)); - context.getProcessorPoolManager().addProcessorPool(pool); - } - - context.getConfigManager().log(logger, ConfigParameterGroup.throughput, ConfigParameterGroup.common, context.isCassandraTest() ? ConfigParameterGroup.cassandra : ConfigParameterGroup.rdbms); - logger.debug("number of insert threads is {}", insertThreads); - logger.debug("insert batch size is {}", insertBatchSize); - logger.debug("numberOfExtraProcessorPools is {}", numberOfExtraProcessorPools); - - logger.info("Starting throughput performance test with {} workflow instances and data size {} chars ...", numbOfWfI, dataSize); - semaphore.acquire(numbOfWfI); - final long startTS = System.currentTimeMillis(); - ExecutorService pool = insertThreads >= 2 ? Executors.newFixedThreadPool(insertThreads) : null; - List> batch = new ArrayList<>(); - for (int i = 0; i < numbOfWfI; i++) { - String ppoolId = "P#DEFAULT"; - if (numberOfExtraProcessorPools > 0) { - ppoolId = "P" + (i % numberOfExtraProcessorPools); - } - batch.add(new WorkflowInstanceDescr<>("org.copperengine.performancetest.workflows.WaitNotifyPerfTestWorkflow", data, engine.createUUID(), 1, ppoolId)); - if (batch.size() == insertBatchSize) { - final List> __batch = batch; - final Runnable r = new Runnable() { - @Override - public void run() { - try { - engine.runBatch(__batch); - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - if (pool != null) - pool.execute(r); - else - r.run(); - batch = new ArrayList<>(); - } - } - if (!batch.isEmpty()) { - engine.runBatch(batch); - } - if (pool != null) { - pool.shutdown(); - pool.awaitTermination(10, TimeUnit.MINUTES); - } - - logger.info("Workflow instances started, waiting..."); - semaphore.acquire(numbOfWfI); - final long et = System.currentTimeMillis() - startTS; - final long avgWaitNotifyPerSecond = numbOfWfI * 10L * 1000L / et; - logger.info("Finished performance test with {} workflow instances in {} msec ==> {} wait/notify cycles per second", numbOfWfI, et, avgWaitNotifyPerSecond); - - Thread.sleep(5000); // drain the batcher - logger.info("statistics:\n{}", context.getStatisticsCollector().print()); - - } catch (Exception e) { - logger.error("performance test failed", e); - } - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/SavepointPerfTestWorkflow.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/SavepointPerfTestWorkflow.java deleted file mode 100644 index 7c29d710..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/SavepointPerfTestWorkflow.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.workflows; - -import java.util.concurrent.TimeUnit; - -import org.copperengine.core.AutoWire; -import org.copperengine.core.Interrupt; -import org.copperengine.core.monitoring.RuntimeStatisticsCollector; -import org.copperengine.core.persistent.PersistentWorkflow; -import org.copperengine.core.util.Backchannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SavepointPerfTestWorkflow extends PersistentWorkflow { - - private static final long serialVersionUID = 1L; - - private static final Logger logger = LoggerFactory.getLogger(SavepointPerfTestWorkflow.class); - - private transient RuntimeStatisticsCollector statisticsCollector; - private transient Backchannel backchannel; - - @AutoWire - public void setBackchannel(Backchannel backchannel) { - this.backchannel = backchannel; - } - - @AutoWire - public void setStatisticsCollector(RuntimeStatisticsCollector statisticsCollector) { - this.statisticsCollector = statisticsCollector; - } - - @Override - public void main() throws Interrupt { - logger.debug("Starting...."); - for (int i = 0; i < 10; i++) { - final long startTS = System.nanoTime(); - savepoint(); - final long etNanos = System.nanoTime() - startTS; - statisticsCollector.submit("savepoint.latency", 1, etNanos, TimeUnit.NANOSECONDS); - logger.debug("Savepoint took {} msec", (double) etNanos / 1000000.0d); - } - logger.debug("Finished!"); - backchannel.notify(getId(), getId()); - } -} diff --git a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/WaitNotifyPerfTestWorkflow.java b/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/WaitNotifyPerfTestWorkflow.java deleted file mode 100644 index 0083f74d..00000000 --- a/projects/copper-performance-test/src/main/java/org/copperengine/performancetest/workflows/WaitNotifyPerfTestWorkflow.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2002-2017 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.performancetest.workflows; - -import java.util.concurrent.Semaphore; - -import org.copperengine.core.AutoWire; -import org.copperengine.core.Interrupt; -import org.copperengine.core.Response; -import org.copperengine.core.WaitMode; -import org.copperengine.core.persistent.PersistentWorkflow; -import org.copperengine.performancetest.impl.MockAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WaitNotifyPerfTestWorkflow extends PersistentWorkflow { - - private static final long serialVersionUID = 1L; - - private static final Logger logger = LoggerFactory.getLogger(WaitNotifyPerfTestWorkflow.class); - - private transient Semaphore semaphore; - private transient MockAdapter mockAdapter; - - @AutoWire - public void setSemaphore(Semaphore semaphore) { - this.semaphore = semaphore; - } - - @AutoWire - public void setMockAdapter(MockAdapter mockAdapter) { - this.mockAdapter = mockAdapter; - } - - @Override - public void main() throws Interrupt { - logger.debug("Starting...."); - for (int i = 0; i < 10; i++) { - final String cid = getEngine().createUUID(); - mockAdapter.foo(getData(), cid, 50); - inner: for (;;) { - wait(WaitMode.ALL, 10000, cid); - Response r = getAndRemoveResponse(cid); - if (r.isTimeout()) { - logger.warn("Timeout"); - } - else { - break inner; - } - } - } - logger.debug("Finished!"); - semaphore.release(); - } -} diff --git a/projects/copper-performance-test/src/main/resources/log4j.properties b/projects/copper-performance-test/src/main/resources/log4j.properties deleted file mode 100644 index 8ae1dbbe..00000000 --- a/projects/copper-performance-test/src/main/resources/log4j.properties +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright 2002-2015 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Set root logger level to DEBUG and its only appender to A1. -log4j.rootLogger=WARN, A2, A1 - -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender -log4j.appender.A2=org.apache.log4j.FileAppender -log4j.appender.StatisticsAppender=org.apache.log4j.FileAppender - -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n - -log4j.appender.A2.File=copper-perftest_${ts}.log -log4j.appender.A2.layout=org.apache.log4j.PatternLayout -log4j.appender.A2.layout.ConversionPattern=%d{yyyy.MM.dd HH:mm:ss,SSS} %-5p [%t] %c [%X{request}] - %m%n -log4j.appender.A2.append=false - -log4j.logger.org.copperengine.performancetest=INFO -#log4j.logger.org.copperengine.core.persistent.ScottyDBStorage=INFO -#log4j.logger.org.copperengine.core.persistent.OracleDialect=INFO -#log4j.logger.org.copperengine.core.persistent.hybrid.HybridDBStorage=INFO -#log4j.logger.org.copperengine.core.persistent.PersistentPriorityProcessorPool=INFO -#log4j.logger.org.copperengine.core.common.Processor=TRACE -#log4j.logger.stat=INFO diff --git a/projects/copper-performance-test/src/main/resources/performancetest.austermann.properties b/projects/copper-performance-test/src/main/resources/performancetest.austermann.properties deleted file mode 100644 index ccb71126..00000000 --- a/projects/copper-performance-test/src/main/resources/performancetest.austermann.properties +++ /dev/null @@ -1,50 +0,0 @@ -# -# Copyright 2002-2017 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#ds.jdbcURL=jdbc:oracle:thin:COPPER/COPPER@vmdocker1.scoop-gmbh.de:15210:ORCL - -#ds.jdbcURL=jdbc:oracle:thin:COPPER2/COPPER2@localhost:1521:orcl11g - -#ds.jdbcURL=jdbc:oracle:thin:COPPER/COPPER@localhost:1521:orcl11g - -#ds.jdbcURL=jdbc:h2:mem:copperPerfTestH2DB;MVCC=TRUE -#ds.driverClass=org.h2.Driver - -ds.jdbcURL=jdbc:postgresql://localhost:5432/postgres -ds.user=copper -ds.password=copper4711 - -#ds.jdbcURL=jdbc:mysql://localhost/COPPER2 -#ds.user=root -#ds.password=geheim - -#batcher.numberOfThreads= - -#cassandra.hosts=nuc1.scoop-gmbh.de,nuc2.scoop-gmbh.de -#cassandra.port= -#cassandra.keyspace=copper - -throughput.numberOfWfI=5000 -throughput.dataSize=8000 -throughput.numberOfInsertThreads=4 -throughput.batchSize=100 -throughput.numberOfExtraProcPools=0 - -compression=false -mockAdapter.numberOfThreads=8 -procPool.numberOfThreads=8 - - diff --git a/projects/copper-performance-test/src/main/resources/performancetest.cassandra.properties b/projects/copper-performance-test/src/main/resources/performancetest.cassandra.properties deleted file mode 100644 index ef07af51..00000000 --- a/projects/copper-performance-test/src/main/resources/performancetest.cassandra.properties +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright 2002-2017 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#ds.jdbcURL=jdbc:oracle:thin:COPPER/COPPER@vmdocker1.scoop-gmbh.de:15210:ORCL - -#ds.jdbcURL=jdbc:oracle:thin:COPPER2/COPPER2@localhost:1521:orcl11g -#ds.user=COPPER2 -#ds.password=COPPER2 -batcher.numberOfThreads= - -cassandra.hosts=nuc1.scoop-gmbh.de,nuc2.scoop-gmbh.de -cassandra.port= -cassandra.keyspace=copper - -throughput.numberOfWfI=20000 -throughput.dataSize=50 -throughput.numberOfInsertThreads=16 -throughput.batchSize=1 - -compression=false -mockAdapter.numberOfThreads=16 -procPool.numberOfThreads=64 - - diff --git a/projects/copper-performance-test/src/main/resources/performancetest.default.properties b/projects/copper-performance-test/src/main/resources/performancetest.default.properties deleted file mode 100644 index ef01704a..00000000 --- a/projects/copper-performance-test/src/main/resources/performancetest.default.properties +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2002-2017 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - diff --git a/projects/copper-performance-test/src/main/resources/performancetest.oracle.properties b/projects/copper-performance-test/src/main/resources/performancetest.oracle.properties deleted file mode 100644 index 3d113bd6..00000000 --- a/projects/copper-performance-test/src/main/resources/performancetest.oracle.properties +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright 2002-2017 SCOOP Software GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#ds.jdbcURL=jdbc:oracle:thin:COPPER/COPPER@vmdocker1.scoop-gmbh.de:15210:ORCL - -ds.jdbcURL=jdbc:oracle:thin:COPPER2/COPPER2@localhost:1521:orcl11g -batcher.numberOfThreads= - -#cassandra.hosts=nuc1.scoop-gmbh.de,nuc2.scoop-gmbh.de -#cassandra.port= -#cassandra.keyspace=copper - -throughput.numberOfWfI=20000 -throughput.dataSize=50 -throughput.numberOfInsertThreads=2 -throughput.batchSize=100 - -compression=false -mockAdapter.numberOfThreads=8 -procPool.numberOfThreads=8 - - diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/audit/SpringAuditTrailTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/audit/SpringAuditTrailTest.java deleted file mode 100644 index 751a0bad..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/audit/SpringAuditTrailTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.audit; - -import org.copperengine.core.audit.AbstractAuditTrail; -import org.copperengine.spring.audit.SpringTxnAuditTrail; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class SpringAuditTrailTest extends AuditTrailTestBase { - - private static final Logger logger = LoggerFactory.getLogger(SpringAuditTrailTest.class); - - - @Override - AbstractAuditTrail getTestAuditTrail() throws Exception { - return new SpringTxnAuditTrail(); - } -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BasePersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BasePersistentWorkflowTest.java index 8797d18a..776d8b29 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BasePersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BasePersistentWorkflowTest.java @@ -44,7 +44,6 @@ import org.copperengine.core.WorkflowFactory; import org.copperengine.core.WorkflowInstanceDescr; import org.copperengine.core.audit.AuditTrailEvent; -import org.copperengine.spring.audit.SpringTxnAuditTrail; import org.copperengine.core.audit.CompressedBase64PostProcessor; import org.copperengine.core.audit.DummyPostProcessor; import org.copperengine.core.db.utility.RetryingTransaction; @@ -529,63 +528,6 @@ protected Void execute() throws Exception { assertEquals(0, engine.getNumberOfWorkflowInstances()); } - public void testCompressedAuditTrail(String dsContext) throws Exception { - assumeFalse(skipTests()); - logger.info("running testCompressedAuditTrail"); - final int NUMB = 20; - final String DATA = createTestData(50); - final ConfigurableApplicationContext context = createContext(dsContext); - context.getBean(SpringTxnAuditTrail.class).setMessagePostProcessor(new CompressedBase64PostProcessor()); - cleanDB(context.getBean(DataSource.class)); - final PersistentScottyEngine engine = context.getBean(PersistentScottyEngine.class); - engine.startup(); - final BackChannelQueue backChannelQueue = context.getBean(BackChannelQueue.class); - try { - assertEquals(EngineState.STARTED, engine.getEngineState()); - - for (int i = 0; i < NUMB; i++) { - engine.run(PersistentUnitTestWorkflow_NAME, DATA); - } - - for (int i = 0; i < NUMB; i++) { - WorkflowResult x = backChannelQueue.dequeue(DEQUEUE_TIMEOUT, TimeUnit.SECONDS); - assertNotNull(x); - assertNotNull(x.getResult()); - assertNotNull(x.getResult().toString().length() == DATA.length()); - assertNull(x.getException()); - } - Thread.sleep(1000); - - new RetryingTransaction(context.getBean(DataSource.class)) { - @Override - protected Void execute() throws Exception { - Statement stmt = createStatement(getConnection()); - ResultSet rs = stmt.executeQuery("select unique message from (select dbms_lob.substr(long_message, 4000, 1 ) message from COP_AUDIT_TRAIL_EVENT) order by 1 asc"); - assertTrue(rs.next()); - // logger.info("\""+new CompressedBase64PostProcessor().deserialize(rs.getString(1))+"\""); - // System.out.println(new CompressedBase64PostProcessor().deserialize(rs.getString(1))); - assertEquals("finished", new CompressedBase64PostProcessor().deserialize(rs.getString(1))); - assertTrue(rs.next()); - assertEquals("foo successfully called", new CompressedBase64PostProcessor().deserialize(rs.getString(1))); - // System.out.println(new CompressedBase64PostProcessor().deserialize(rs.getString(1))); - assertFalse(rs.next()); - rs.close(); - stmt.close(); - return null; - } - }.run(); - - } catch (Exception e) { - logger.error("testCompressedAuditTrail failed", e); - throw e; - } finally { - closeContext(context); - } - assertEquals(EngineState.STOPPED, engine.getEngineState()); - assertEquals(0, engine.getNumberOfWorkflowInstances()); - - } - public void testAutoCommit(String dsContext) throws Exception { assumeFalse(skipTests()); logger.info("running testAutoCommit"); @@ -613,22 +555,6 @@ private static String createTestMessage(int size) { return msg; } - public void testAuditTrailUncompressed(String dsContext) throws Exception { - assumeFalse(skipTests()); - logger.info("running testAuditTrailSmallData"); - final ConfigurableApplicationContext context = createContext(dsContext); - try { - org.copperengine.spring.audit.SpringTxnAuditTrail auditTrail = context.getBean(org.copperengine.spring.audit.SpringTxnAuditTrail.class); - auditTrail.setMessagePostProcessor(new DummyPostProcessor()); - auditTrail.synchLog(1, new Date(), "4711", dsContext, "4711", "4711", "4711", null, "TEXT"); - auditTrail.synchLog(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(500), "TEXT"); - auditTrail.synchLog(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(5000), "TEXT"); - auditTrail.synchLog(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(50000), "TEXT"); - } finally { - closeContext(context); - } - } - public void testErrorHandlingWithWaitHook(String dsContext) throws Exception { assumeFalse(skipTests()); final ConfigurableApplicationContext context = createContext(dsContext); @@ -662,44 +588,6 @@ protected Void execute() throws Exception { assertEquals(0, engine.getNumberOfWorkflowInstances()); } - public void testAuditTrailCustomSeqNr(String dsContext) throws Exception { - assumeFalse(skipTests()); - logger.info("running testAuditTrailCustomSeqNr"); - final ConfigurableApplicationContext context = createContext(dsContext); - try { - cleanDB(context.getBean(DataSource.class)); - org.copperengine.spring.audit.SpringTxnAuditTrail auditTrail = context.getBean(org.copperengine.spring.audit.SpringTxnAuditTrail.class); - auditTrail.setMessagePostProcessor(new DummyPostProcessor()); - long seqNr = 1; - auditTrail.synchLog(new AuditTrailEvent(1, new Date(), "4711", dsContext, "4711", "4711", "4711", null, "TEXT", seqNr++)); - auditTrail.synchLog(new AuditTrailEvent(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(500), "TEXT", seqNr++)); - auditTrail.synchLog(new AuditTrailEvent(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(5000), "TEXT", seqNr++)); - auditTrail.synchLog(new AuditTrailEvent(1, new Date(), "4711", dsContext, "4711", "4711", "4711", createTestMessage(50000), "TEXT", seqNr++)); - // check - new RetryingTransaction(context.getBean(DataSource.class)) { - @Override - protected Void execute() throws Exception { - Statement stmt = createStatement(getConnection()); - ResultSet rs = stmt.executeQuery("select seq_id from COP_AUDIT_TRAIL_EVENT order by seq_id"); - assertTrue(rs.next()); - assertEquals(1, rs.getLong(1)); - assertTrue(rs.next()); - assertEquals(2, rs.getLong(1)); - assertTrue(rs.next()); - assertEquals(3, rs.getLong(1)); - assertTrue(rs.next()); - assertEquals(4, rs.getLong(1)); - assertFalse(rs.next()); - rs.close(); - stmt.close(); - return null; - } - }.run(); - } finally { - closeContext(context); - } - } - public void testNotifyWithoutEarlyResponseHandling(String dsContext) throws Exception { assumeFalse(skipTests()); logger.info("running testNotifyWithoutEarlyResponseHandling"); diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BaseSpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BaseSpringTxnPersistentWorkflowTest.java deleted file mode 100644 index 79b9d92d..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/BaseSpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; - -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.concurrent.TimeUnit; - -import javax.sql.DataSource; - -import org.copperengine.core.EngineState; -import org.copperengine.core.db.utility.RetryingTransaction; -import org.copperengine.core.persistent.PersistentScottyEngine; -import org.copperengine.regtest.test.backchannel.BackChannelQueue; -import org.copperengine.regtest.test.backchannel.WorkflowResult; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.FileSystemXmlApplicationContext; - -public class BaseSpringTxnPersistentWorkflowTest extends BasePersistentWorkflowTest { - - protected ConfigurableApplicationContext createContext(String dsContext) { - String prefix = "src/test/resources/"; - return new FileSystemXmlApplicationContext(new String[] { - prefix + dsContext, - prefix + "SpringTxnPersistentWorkflowTest/persistent-engine-unittest-context.xml", - prefix + "unittest-context.xml" }); - } - - public void testSpringTxnUnitTestWorkflow(String dsContext) throws Exception { - assumeFalse(skipTests()); - final ConfigurableApplicationContext context = createContext(dsContext); - cleanDB(context.getBean(DataSource.class)); - final PersistentScottyEngine engine = context.getBean(PersistentScottyEngine.class); - final BackChannelQueue backChannelQueue = context.getBean(BackChannelQueue.class); - try { - engine.startup(); - engine.run("org.copperengine.regtest.test.persistent.springtxn.SpringTxnUnitTestWorkflow", "TestData"); - WorkflowResult x = backChannelQueue.dequeue(60, TimeUnit.SECONDS); - assertNotNull(x); - assertNotNull(x.getResult()); - assertNull(x.getException()); - - // check - new RetryingTransaction(context.getBean(DataSource.class)) { - @Override - protected Void execute() throws Exception { - Statement stmt = getConnection().createStatement(); - ResultSet rs = stmt.executeQuery("select count(*) from COP_AUDIT_TRAIL_EVENT"); - assertTrue(rs.next()); - int c = rs.getInt(1); - assertEquals(7, c); - rs.close(); - stmt.close(); - return null; - } - }.run(); - } finally { - closeContext(context); - } - assertEquals(EngineState.STOPPED, engine.getEngineState()); - assertEquals(0, engine.getNumberOfWorkflowInstances()); - } -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbPersistentWorkflowTest.java index 612a04b3..f459cd5b 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbPersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbPersistentWorkflowTest.java @@ -138,12 +138,21 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000 + ); } @Test diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbSpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbSpringTxnPersistentWorkflowTest.java deleted file mode 100644 index 25489d18..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/DerbyDbSpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import javax.sql.DataSource; - -import org.junit.After; -import org.junit.Test; -import org.springframework.context.ConfigurableApplicationContext; - -public class DerbyDbSpringTxnPersistentWorkflowTest extends BaseSpringTxnPersistentWorkflowTest { - - private static final String DS_CONTEXT = "/datasources/datasource-derbydb.xml"; - - @Override - void cleanDB(DataSource ds) throws Exception { - super.cleanDB(ds); - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void testAsynchResponse() throws Exception { - super.testAsynchResponse(DS_CONTEXT); - } - - @Test - public void testAsynchResponseLargeData() throws Exception { - super.testAsynchResponseLargeData(DS_CONTEXT, 10000); - } - - @Test - public void testWithConnection() throws Exception { - super.testWithConnection(DS_CONTEXT); - } - - @Test - public void testWithConnectionBulkInsert() throws Exception { - super.testWithConnectionBulkInsert(DS_CONTEXT); - } - - @Test - public void testTimeouts() throws Exception { - super.testTimeouts(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine() throws Exception { - super.testErrorHandlingInCoreEngine(DS_CONTEXT); - } - - @Test - public void testParentChildWorkflow() throws Exception { - super.testParentChildWorkflow(DS_CONTEXT); - } - - @Test - public void testErrorKeepWorkflowInstanceInDB() throws Exception { - super.testErrorKeepWorkflowInstanceInDB(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine_restartAll() throws Exception { - super.testErrorHandlingInCoreEngine_restartAll(DS_CONTEXT); - } - - // public void testCompressedAuditTrail() throws Exception { - // super.testCompressedAuditTrail(DS_CONTEXT); - // } - - @Test - public void testAutoCommit() throws Exception { - super.testAutoCommit(DS_CONTEXT); - } - - @Test - public void testAuditTrailUncompressed() throws Exception { - super.testAuditTrailUncompressed(DS_CONTEXT); - } - - @Test - public void testErrorHandlingWithWaitHook() throws Exception { - super.testErrorHandlingWithWaitHook(DS_CONTEXT); - } - - @Test(expected = UnsupportedOperationException.class) - public void testAuditTrailCustomSeqNr() throws Exception { - super.testAuditTrailCustomSeqNr(DS_CONTEXT); - } - - @Test - public void testSpringTxnUnitTestWorkflow() throws Exception { - super.testSpringTxnUnitTestWorkflow(DS_CONTEXT); - } - - @Override - protected void closeContext(final ConfigurableApplicationContext context) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // ignore - } - context.close(); - } - - @Test - public void testFailOnDuplicateInsert() throws Exception { - super.testFailOnDuplicateInsert(DS_CONTEXT); - } - -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java index 96b9365a..42953054 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java @@ -147,12 +147,21 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000 + ); } @Test diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2SpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2SpringTxnPersistentWorkflowTest.java deleted file mode 100644 index 41f6eb4d..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2SpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import javax.sql.DataSource; - -import org.copperengine.core.persistent.H2Dialect; -import org.junit.After; -import org.junit.Test; -import org.springframework.context.ConfigurableApplicationContext; - -public class H2SpringTxnPersistentWorkflowTest extends BaseSpringTxnPersistentWorkflowTest { - - private static final String DS_CONTEXT = "/datasources/datasource-h2.xml"; - - @Override - void cleanDB(DataSource ds) throws Exception { - H2Dialect.checkAndCreateSchema(ds); - super.cleanDB(ds); - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void testAsynchResponse() throws Exception { - super.testAsynchResponse(DS_CONTEXT); - } - - @Test - public void testAsynchResponseLargeData() throws Exception { - super.testAsynchResponseLargeData(DS_CONTEXT, 10000); - } - - @Test - public void testWithConnection() throws Exception { - super.testWithConnection(DS_CONTEXT); - } - - @Test - public void testWithConnectionBulkInsert() throws Exception { - super.testWithConnectionBulkInsert(DS_CONTEXT); - } - - @Test - public void testTimeouts() throws Exception { - super.testTimeouts(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine() throws Exception { - super.testErrorHandlingInCoreEngine(DS_CONTEXT); - } - - @Test - public void testParentChildWorkflow() throws Exception { - super.testParentChildWorkflow(DS_CONTEXT); - } - - @Test - public void testErrorKeepWorkflowInstanceInDB() throws Exception { - super.testErrorKeepWorkflowInstanceInDB(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine_restartAll() throws Exception { - super.testErrorHandlingInCoreEngine_restartAll(DS_CONTEXT); - } - - // public void testCompressedAuditTrail() throws Exception { - // super.testCompressedAuditTrail(DS_CONTEXT); - // } - - @Test - public void testAutoCommit() throws Exception { - super.testAutoCommit(DS_CONTEXT); - } - - @Test - public void testAuditTrailUncompressed() throws Exception { - super.testAuditTrailUncompressed(DS_CONTEXT); - } - - @Test - public void testErrorHandlingWithWaitHook() throws Exception { - super.testErrorHandlingWithWaitHook(DS_CONTEXT); - } - - @Test(expected = UnsupportedOperationException.class) - public void testAuditTrailCustomSeqNr() throws Exception { - super.testAuditTrailCustomSeqNr(DS_CONTEXT); - } - - @Test - public void testSpringTxnUnitTestWorkflow() throws Exception { - super.testSpringTxnUnitTestWorkflow(DS_CONTEXT); - } - - @Override - protected void closeContext(final ConfigurableApplicationContext context) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // ignore - } - context.close(); - } - - @Test - public void testFailOnDuplicateInsert() throws Exception { - super.testFailOnDuplicateInsert(DS_CONTEXT); - } - -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlPersistentWorkflowTest.java index a7abe43c..a65335a6 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlPersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlPersistentWorkflowTest.java @@ -144,12 +144,20 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000); } @Test diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlSpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlSpringTxnPersistentWorkflowTest.java deleted file mode 100644 index 705a658f..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/MySqlSpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MySqlSpringTxnPersistentWorkflowTest extends BaseSpringTxnPersistentWorkflowTest { - - private static final String DS_CONTEXT = "/datasources/datasource-mysql.xml"; - private static final Logger logger = LoggerFactory.getLogger(MySqlPersistentWorkflowTest.class); - - private static boolean dbmsAvailable = false; - static { - dbmsAvailable = new PersistentEngineTestContext(DataSourceType.MySQL, false).isDbmsAvailable(); - } - - @Override - protected boolean skipTests() { - return !dbmsAvailable; - } - - @Test - public void testAsynchResponse() throws Exception { - super.testAsynchResponse(DS_CONTEXT); - } - - @Test - public void testAsynchResponseLargeData() throws Exception { - super.testAsynchResponseLargeData(DS_CONTEXT, 65536); - } - - @Test - public void testWithConnection() throws Exception { - super.testWithConnection(DS_CONTEXT); - } - - @Test - public void testWithConnectionBulkInsert() throws Exception { - super.testWithConnectionBulkInsert(DS_CONTEXT); - } - - @Test - public void testTimeouts() throws Exception { - super.testTimeouts(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine() throws Exception { - super.testErrorHandlingInCoreEngine(DS_CONTEXT); - } - - @Test - public void testParentChildWorkflow() throws Exception { - super.testParentChildWorkflow(DS_CONTEXT); - } - - @Test - public void testErrorKeepWorkflowInstanceInDB() throws Exception { - super.testErrorKeepWorkflowInstanceInDB(DS_CONTEXT); - } - - @Test - public void testAuditTrailUncompressed() throws Exception { - super.testAuditTrailUncompressed(DS_CONTEXT); - } - - @Test - public void testErrorHandlingWithWaitHook() throws Exception { - super.testErrorHandlingWithWaitHook(DS_CONTEXT); - } - - @Test - public void testSpringTxnUnitTestWorkflow() throws Exception { - super.testSpringTxnUnitTestWorkflow(DS_CONTEXT); - } - - @Test - public void testAutoCommit() throws Exception { - super.testAutoCommit(DS_CONTEXT); - } - - @Test - public void testFailOnDuplicateInsert() throws Exception { - super.testFailOnDuplicateInsert(DS_CONTEXT); - } -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OraclePersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OraclePersistentWorkflowTest.java index c83f7bf3..4b569680 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OraclePersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OraclePersistentWorkflowTest.java @@ -153,12 +153,21 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000 + ); } @Test public void testJmxQueryWithOffsetWorkflowInstances() throws Exception { diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSimplePersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSimplePersistentWorkflowTest.java index 25dd1e8a..3fd6b6bc 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSimplePersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSimplePersistentWorkflowTest.java @@ -143,12 +143,21 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000 + ); } @Test public void testJmxQueryWithOffsetWorkflowInstances() throws Exception { diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSpringTxnPersistentWorkflowTest.java deleted file mode 100644 index af10bbcf..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/OracleSpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OracleSpringTxnPersistentWorkflowTest extends BaseSpringTxnPersistentWorkflowTest { - - private static final String DS_CONTEXT = "/datasources/datasource-oracle.xml"; - private static final Logger logger = LoggerFactory.getLogger(OracleSpringTxnPersistentWorkflowTest.class); - - private static boolean dbmsAvailable = false; - static { - dbmsAvailable = new PersistentEngineTestContext(DataSourceType.Oracle, false).isDbmsAvailable(); - } - - @Override - protected boolean skipTests() { - return !dbmsAvailable; - } - - @Test - public void testAsynchResponse() throws Exception { - super.testAsynchResponse(DS_CONTEXT); - } - - @Test - public void testAsynchResponseLargeData() throws Exception { - super.testAsynchResponseLargeData(DS_CONTEXT, 65536); - } - - @Test - public void testWithConnection() throws Exception { - super.testWithConnection(DS_CONTEXT); - } - - @Test - public void testWithConnectionBulkInsert() throws Exception { - super.testWithConnectionBulkInsert(DS_CONTEXT); - } - - @Test - public void testTimeouts() throws Exception { - super.testTimeouts(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine() throws Exception { - super.testErrorHandlingInCoreEngine(DS_CONTEXT); - } - - @Test - public void testParentChildWorkflow() throws Exception { - super.testParentChildWorkflow(DS_CONTEXT); - } - - @Test - public void testErrorKeepWorkflowInstanceInDB() throws Exception { - super.testErrorKeepWorkflowInstanceInDB(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine_restartAll() throws Exception { - super.testErrorHandlingInCoreEngine_restartAll(DS_CONTEXT); - } - - @Test - public void testCompressedAuditTrail() throws Exception { - super.testCompressedAuditTrail(DS_CONTEXT); - } - - @Test - public void testAutoCommit() throws Exception { - super.testAutoCommit(DS_CONTEXT); - } - - @Test - public void testAuditTrailUncompressed() throws Exception { - super.testAuditTrailUncompressed(DS_CONTEXT); - } - - @Test - public void testErrorHandlingWithWaitHook() throws Exception { - super.testErrorHandlingWithWaitHook(DS_CONTEXT); - } - - @Test - public void testAuditTrailCustomSeqNr() throws Exception { - super.testAuditTrailCustomSeqNr(DS_CONTEXT); - } - - @Test - public void testSpringTxnUnitTestWorkflow() throws Exception { - super.testSpringTxnUnitTestWorkflow(DS_CONTEXT); - } - - @Test - public void testFailOnDuplicateInsert() throws Exception { - super.testFailOnDuplicateInsert(DS_CONTEXT); - } -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLPersistentWorkflowTest.java index 6ded321a..ddcff08a 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLPersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLPersistentWorkflowTest.java @@ -143,12 +143,21 @@ public void testDeleteWaitingWorkflowInstances() throws Exception { @Test public void testDeleteFilteredWorkflowInstance() throws Exception { - super.testDeleteFilteredWorkflowInstance(DS_CONTEXT); + super.testDeleteFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000 + ); } @Test public void testRestartFilteredWorkflowInstance() throws Exception { - super.testRestartFilteredWorkflowInstance(DS_CONTEXT); + super.testRestartFilteredWorkflowInstance( + DS_CONTEXT, + 5_000, + 10_000, + 5_000 + ); } @Test diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLSpringTxnPersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLSpringTxnPersistentWorkflowTest.java deleted file mode 100644 index 8c1ede7b..00000000 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PostgreSQLSpringTxnPersistentWorkflowTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.regtest.test.persistent; - -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PostgreSQLSpringTxnPersistentWorkflowTest extends BaseSpringTxnPersistentWorkflowTest { - - private static final String DS_CONTEXT = "/datasources/datasource-postgresql.xml"; - private static final Logger logger = LoggerFactory.getLogger(MySqlPersistentWorkflowTest.class); - - private static boolean dbmsAvailable = false; - static { - dbmsAvailable = new PersistentEngineTestContext(DataSourceType.Postgres, false).isDbmsAvailable(); - } - - @Override - protected boolean skipTests() { - return !dbmsAvailable; - } - - @Test - public void testAsynchResponse() throws Exception { - super.testAsynchResponse(DS_CONTEXT); - } - - @Test - public void testAsynchResponseLargeData() throws Exception { - super.testAsynchResponseLargeData(DS_CONTEXT, 65536); - } - - @Test - public void testWithConnection() throws Exception { - super.testWithConnection(DS_CONTEXT); - } - - @Test - public void testWithConnectionBulkInsert() throws Exception { - super.testWithConnectionBulkInsert(DS_CONTEXT); - } - - @Test - public void testTimeouts() throws Exception { - super.testTimeouts(DS_CONTEXT); - } - - @Test - public void testErrorHandlingInCoreEngine() throws Exception { - super.testErrorHandlingInCoreEngine(DS_CONTEXT); - } - - @Test - public void testParentChildWorkflow() throws Exception { - super.testParentChildWorkflow(DS_CONTEXT); - } - - @Test - public void testErrorKeepWorkflowInstanceInDB() throws Exception { - super.testErrorKeepWorkflowInstanceInDB(DS_CONTEXT); - } - - @Test - public void testAuditTrailUncompressed() throws Exception { - super.testAuditTrailUncompressed(DS_CONTEXT); - } - - @Test - public void testErrorHandlingWithWaitHook() throws Exception { - super.testErrorHandlingWithWaitHook(DS_CONTEXT); - } - - @Test - public void testSpringTxnUnitTestWorkflow() throws Exception { - super.testSpringTxnUnitTestWorkflow(DS_CONTEXT); - } - - @Test - public void testAutoCommit() throws Exception { - super.testAutoCommit(DS_CONTEXT); - } - - @Test - public void testFailOnDuplicateInsert() throws Exception { - super.testFailOnDuplicateInsert(DS_CONTEXT); - } - -} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java index f07cbab5..145df461 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java @@ -1453,7 +1453,7 @@ protected Void execute() throws Exception { assertEquals(0, engine.getNumberOfWorkflowInstances()); } - public void testDeleteFilteredWorkflowInstance(DataSourceType dsType) throws Exception { + public void testDeleteFilteredWorkflowInstance(DataSourceType dsType, long inbetween, long delay) throws Exception { assumeFalse(skipTests()); final PersistentEngineTestContext context = createContext(dsType); final PersistentScottyEngine engine = context.getEngine(); @@ -1467,17 +1467,19 @@ public void testDeleteFilteredWorkflowInstance(DataSourceType dsType) throws Exc waitingWorkflow.setId(engine.createUUID()); engine.run(waitingWorkflow); long thisExactMoment = new Date().getTime(); - Date inbetweenFirstWFs = new Date(thisExactMoment + 120000); + Date inbetweenFirstWFs = new Date(thisExactMoment + inbetween); - Thread.sleep(280000); // the next workflow to be created will have a later Timestamp + logger.info("Sleep for {} millis.", delay); + Thread.sleep(delay); // the next workflow to be created will have a later Timestamp WorkflowInstanceDescr brokenWorkflow = new WorkflowInstanceDescr<>(DeleteBrokenTestWF_NAME, 1); brokenWorkflow.setId(engine.createUUID()); engine.run(brokenWorkflow); thisExactMoment = new Date().getTime(); - Date inbetweenSecondWFs = new Date(thisExactMoment + 120000); + Date inbetweenSecondWFs = new Date(thisExactMoment + inbetween); - Thread.sleep(280000); // wait for it to start up / bring workflows to error state + logger.info("Sleep for {} millis.", delay); + Thread.sleep(delay); // wait for it to start up / bring workflows to error state WorkflowInstanceDescr brokenWorkflow2 = new WorkflowInstanceDescr<>(DeleteBrokenTestWF_NAME, 1); brokenWorkflow2.setId(engine.createUUID()); @@ -1541,7 +1543,7 @@ public void testDeleteFilteredWorkflowInstance(DataSourceType dsType) throws Exc assertEquals(0, engine.getNumberOfWorkflowInstances()); } - public void testRestartFilteredWorkflowInstance(DataSourceType dsType) throws Exception { + public void testRestartFilteredWorkflowInstance(DataSourceType dsType, long inbetween, long delay, long after) throws Exception { assumeFalse(skipTests()); final PersistentEngineTestContext context = createContext(dsType); final PersistentScottyEngine engine = context.getEngine(); @@ -1555,16 +1557,17 @@ public void testRestartFilteredWorkflowInstance(DataSourceType dsType) throws Ex engine.run(brokenWorkflow); long thisExactMoment = new Date().getTime(); - Date inbetweenWFs = new Date(thisExactMoment + 120000); + Date inbetweenWFs = new Date(thisExactMoment + inbetween); - Thread.sleep(280000); // the next workflow to be created will have a later Timestamp + logger.info("Sleep for {} millis.", delay); + Thread.sleep(delay); // the next workflow to be created will have a later Timestamp WorkflowInstanceDescr brokenWorkflow2 = new WorkflowInstanceDescr<>(DeleteBrokenTestWF_NAME, 1); brokenWorkflow2.setId(engine.createUUID()); engine.run(brokenWorkflow2); thisExactMoment = new Date().getTime(); - Date afterWFs = new Date(thisExactMoment + 120000); + Date afterWFs = new Date(thisExactMoment + inbetween); Thread.sleep(200); // wait for it to start up / bring workflows to error state @@ -1577,7 +1580,8 @@ public void testRestartFilteredWorkflowInstance(DataSourceType dsType) throws Ex // wait long enough so that when WF is restarted, it is past the point of the previously created // afterWFs time - Thread.sleep(180000); + logger.info("Sleep for {} millis.", after); + Thread.sleep(after); // restarting the first workflow based on it being created before the first timestamp time = new HalfOpenTimeInterval(); diff --git a/projects/copper-regtest/src/test/resources/SpringTxnPersistentWorkflowTest/persistent-engine-unittest-context.xml b/projects/copper-regtest/src/test/resources/SpringTxnPersistentWorkflowTest/persistent-engine-unittest-context.xml deleted file mode 100644 index 978bbed2..00000000 --- a/projects/copper-regtest/src/test/resources/SpringTxnPersistentWorkflowTest/persistent-engine-unittest-context.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - classpath:regtest.default.properties - classpath:regtest.${user.name}.properties - - - - - - - - - - - - - src/workflow/java - - - - - - - - - 3 - - - - - - - - - - P#DEFAULT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java index fe93ed1c..d4388c85 100644 --- a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java +++ b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java @@ -41,7 +41,6 @@ import org.copperengine.regtest.test.MockAdapter; import org.copperengine.regtest.test.backchannel.BackChannelQueue; import org.copperengine.regtest.test.backchannel.WorkflowResult; -import org.copperengine.spring.audit.SpringTxnAuditTrail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/projects/copper-spring/src/main/java/module-info.java b/projects/copper-spring/src/main/java/module-info.java deleted file mode 100644 index 8579491c..00000000 --- a/projects/copper-spring/src/main/java/module-info.java +++ /dev/null @@ -1,16 +0,0 @@ -module org.copperengine.spring { - requires transitive org.copperengine.core; - - requires java.sql; - - requires org.slf4j; - - requires spring.core; - requires transitive spring.jdbc; - requires spring.batch.infrastructure; - requires transitive spring.beans; - requires transitive spring.context; - requires transitive spring.tx; - - exports org.copperengine.spring.audit; -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/JmxExporter.java b/projects/copper-spring/src/main/java/org/copperengine/spring/JmxExporter.java deleted file mode 100644 index 85f7f56c..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/JmxExporter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring; - -import java.util.Map; - -import org.copperengine.core.common.AbstractJmxExporter; -import org.copperengine.management.AuditTrailMXBean; -import org.copperengine.management.AuditTrailQueryMXBean; -import org.copperengine.management.BatcherMXBean; -import org.copperengine.management.DBStorageMXBean; -import org.copperengine.management.DatabaseDialectMXBean; -import org.copperengine.management.ProcessingEngineMXBean; -import org.copperengine.management.ProcessorPoolMXBean; -import org.copperengine.management.StatisticsCollectorMXBean; -import org.copperengine.management.WorkflowRepositoryMXBean; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Automatically exports all COPPER MXBeans, which are available in the Spring Application Context, to the JMX - * MBeanServer. - * - * @author austermann - */ -public class JmxExporter extends AbstractJmxExporter implements ApplicationContextAware { - - private ApplicationContext applicationContext; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - protected Map getWorkflowRepositoryMXBeans() { - return applicationContext.getBeansOfType(WorkflowRepositoryMXBean.class); - } - - @Override - protected Map getProcessingEngineMXBeans() { - return applicationContext.getBeansOfType(ProcessingEngineMXBean.class); - } - - @Override - protected Map getProcessorPoolMXBeans() { - return applicationContext.getBeansOfType(ProcessorPoolMXBean.class); - } - - @Override - protected Map getStatisticsCollectorMXBeans() { - return applicationContext.getBeansOfType(StatisticsCollectorMXBean.class); - } - - @Override - protected Map getAuditTrailMXBeans() { - return applicationContext.getBeansOfType(AuditTrailMXBean.class); - } - - @Override - protected Map getBatcherMXBeans() { - return applicationContext.getBeansOfType(BatcherMXBean.class); - } - - @Override - protected Map getDatabaseDialectMXBeans() { - return applicationContext.getBeansOfType(DatabaseDialectMXBean.class); - } - - @Override - protected Map getDBStorageMXBeans() { - return applicationContext.getBeansOfType(DBStorageMXBean.class); - } - - @Override - protected Map getAuditTrailQueryMXBeans() { - return applicationContext.getBeansOfType(AuditTrailQueryMXBean.class); - } - -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringDependencyInjector.java b/projects/copper-spring/src/main/java/org/copperengine/spring/SpringDependencyInjector.java deleted file mode 100644 index a058e089..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringDependencyInjector.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring; - -import org.copperengine.core.AbstractDependencyInjector; -import org.copperengine.core.persistent.SavepointAware; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Connects SPRING to COPPER. Enables COPPER to inject dependencies into workflow instances using a spring - * container/context. - * - * @author austermann - */ -public class SpringDependencyInjector extends AbstractDependencyInjector implements ApplicationContextAware { - - private ApplicationContext context; - - public SpringDependencyInjector() { - } - - public SpringDependencyInjector(ApplicationContext context) { - this.context = context; - } - - @Override - public String getType() { - return "SPRING"; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - context = applicationContext; - } - - @Override - protected Object getBean(String beanId) { - Object firsttry = context.getBean(beanId); - if (firsttry instanceof SavepointAware) { - Object secondtry = context.getBean(beanId); - if (firsttry == secondtry) { - throw new IllegalStateException(beanId + " scope is not prototype"); - } - } - return firsttry; - - } - -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringEngineStarter.java b/projects/copper-spring/src/main/java/org/copperengine/spring/SpringEngineStarter.java deleted file mode 100644 index f2a66be8..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringEngineStarter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.support.FileSystemXmlApplicationContext; - -/** - * Simple utility class to load and run a Spring FileSystemXmlApplicationContext. - * - * @author austermann - */ -public class SpringEngineStarter { - - private static final Logger logger = LoggerFactory.getLogger(SpringEngineStarter.class); - - public static void main(String[] args) { - if (args.length == 0) { - System.out.println("Usage: " + SpringEngineStarter.class.getName() + " "); - System.exit(-2); - } - try { - new FileSystemXmlApplicationContext(args); - } catch (Exception e) { - logger.error("Startup failed", e); - System.exit(-1); - } - } - -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransaction.java b/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransaction.java deleted file mode 100644 index 89f11f4a..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring; - -import java.sql.Connection; - -import javax.sql.DataSource; - -import org.springframework.jdbc.datasource.DataSourceUtils; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; - -public abstract class SpringTransaction { - - protected abstract void execute(Connection con) throws Exception; - - public void run(PlatformTransactionManager transactionManager, DataSource dataSource, TransactionDefinition def) throws Exception { - TransactionStatus txnStatus = transactionManager.getTransaction(def); - try { - Connection con = DataSourceUtils.getConnection(dataSource); - try { - execute(con); - } finally { - DataSourceUtils.releaseConnection(con, dataSource); - } - } catch (Exception e) { - transactionManager.rollback(txnStatus); - throw e; - } - transactionManager.commit(txnStatus); - } -} \ No newline at end of file diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransactionController.java b/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransactionController.java deleted file mode 100644 index 88eacd1a..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/SpringTransactionController.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring; - -import java.sql.Connection; - -import javax.sql.DataSource; - -import org.copperengine.core.persistent.txn.DatabaseTransaction; -import org.copperengine.core.persistent.txn.Transaction; -import org.copperengine.core.persistent.txn.TransactionController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; - -/** - * Implementation of the {@link TransactionController} interface that internally uses Springs Transaction Management - * - * @author austermann - */ -public class SpringTransactionController implements TransactionController { - - private static final Logger logger = LoggerFactory.getLogger(SpringTransactionController.class); - - private DataSource dataSource; - private PlatformTransactionManager transactionManager; - - public void setTransactionManager(PlatformTransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } - - @SuppressWarnings("unchecked") - @Override - public T run(final DatabaseTransaction txn) throws Exception { - final T[] t = (T[]) new Object[1]; - new SpringTransaction() { - @Override - protected void execute(Connection con) throws Exception { - t[0] = txn.run(con); - } - }.run(transactionManager, dataSource, new DefaultTransactionDefinition()); - return t[0]; - } - - @Override - public T run(Transaction txn) throws Exception { - final TransactionStatus txnStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); - T t = null; - try { - t = txn.run(); - } catch (Exception e) { - logger.error("execution failed - rolling back transaction", e); - transactionManager.rollback(txnStatus); - throw e; - } - transactionManager.commit(txnStatus); - return t; - } - -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/AuditTrailQueryEngine.java b/projects/copper-spring/src/main/java/org/copperengine/spring/audit/AuditTrailQueryEngine.java deleted file mode 100644 index 319bad8c..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/AuditTrailQueryEngine.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring.audit; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.sql.Clob; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.copperengine.core.audit.MessagePostProcessor; -import org.copperengine.management.AuditTrailQueryMXBean; -import org.copperengine.management.model.AuditTrailInfo; -import org.copperengine.management.model.AuditTrailInstanceFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.batch.item.database.PagingQueryProvider; -import org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.support.JdbcDaoSupport; -import org.springframework.util.StringUtils; - -// Use org.copperengine.core.audit.ScottyAuditTrailQueryEngine instead -@Deprecated -public class AuditTrailQueryEngine extends JdbcDaoSupport implements AuditTrailQueryMXBean { - private static final Logger logger = LoggerFactory.getLogger(AuditTrailQueryEngine.class); - - private MessagePostProcessor messagePostProcessor; - - @Override - public List getAuditTrails(String transactionId, String conversationId, String correlationId, Integer level, int maxResult) { - - SqlPagingQueryProviderFactoryBean factory = new SqlPagingQueryProviderFactoryBean(); - - String sortClause = "SEQ_ID"; - String whereClause = "where 1=1 "; - List args = new ArrayList(); - - if (level != null) { - whereClause += " and LOGLEVEL <= ? "; - sortClause = "LOGLEVEL"; - args.add(level); - } - if (StringUtils.hasText(correlationId)) { - whereClause += " and CORRELATION_ID = ? "; - sortClause = "CORRELATION_ID"; - args.add(correlationId); - } - - if (StringUtils.hasText(conversationId)) { - whereClause += " and CONVERSATION_ID = ? "; - sortClause = "CONVERSATION_ID"; - args.add(conversationId); - } - - if (StringUtils.hasText(transactionId)) { - whereClause += " and TRANSACTION_ID = ? "; - sortClause = "TRANSACTION_ID"; - args.add(transactionId); - } - - String selectClause = "select " - + "SEQ_ID," - + "TRANSACTION_ID," - + "CONVERSATION_ID," - + "CORRELATION_ID," - + "OCCURRENCE," - + "LOGLEVEL," - + "CONTEXT," - + "INSTANCE_ID," - + "MESSAGE_TYPE"; - - factory.setDataSource(getDataSource()); - factory.setFromClause("from COP_AUDIT_TRAIL_EVENT "); - - factory.setSelectClause(selectClause); - - factory.setWhereClause(whereClause); - factory.setSortKey(sortClause); - - PagingQueryProvider queryProvider; - try { - queryProvider = (PagingQueryProvider) factory.getObject(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - return null; - } - if(queryProvider == null) return null; - String query = queryProvider.generateFirstPageQuery(maxResult); - - // this.getJdbcTemplate().setQueryTimeout(1000); - - long start = System.currentTimeMillis(); - RowMapper rowMapper = new RowMapper() { - - public AuditTrailInfo mapRow(ResultSet rs, int arg1) - throws SQLException { - - return new AuditTrailInfo( - rs.getLong("SEQ_ID"), - rs.getString("TRANSACTION_ID"), - rs.getString("CONVERSATION_ID"), - rs.getString("CORRELATION_ID"), - rs.getTimestamp("OCCURRENCE").getTime(), - rs.getInt("LOGLEVEL"), - rs.getString("CONTEXT"), - rs.getString("INSTANCE_ID"), - rs.getString("MESSAGE_TYPE") - ); - } - - }; - JdbcTemplate jdbcTemplate = getJdbcTemplate(); - List res = (jdbcTemplate != null) - ? jdbcTemplate.query(query, rowMapper, args.toArray()) - : Collections.emptyList(); - - long end = System.currentTimeMillis(); - - logger.info("query took: " + (end - start) + " ms : " + query); - - return res; - } - - @Deprecated - public byte[] getMessage(long id) { - String customSelect = "select LONG_MESSAGE from COP_AUDIT_TRAIL_EVENT where SEQ_ID = ? "; - - ResultSetExtractor rse = new ResultSetExtractor() { - - @Override - public byte[] extractData(ResultSet rs) throws SQLException, - DataAccessException { - rs.next(); - return convertToArray(rs.getBinaryStream("LONG_MESSAGE")); - } - - }; - - JdbcTemplate jdbcTemplate = getJdbcTemplate(); - return (jdbcTemplate != null) ? jdbcTemplate.query(customSelect, rse, new Object[] { id }) : null; - } - - @Override - public List getAuditTrails(AuditTrailInstanceFilter filter) { - throw new UnsupportedOperationException("Not supported. Use org.copperengine.core.audit.ScottyAuditTrailQueryEngine instead"); - } - - @Override - public int countAuditTrails(AuditTrailInstanceFilter filter) { - throw new UnsupportedOperationException("Not supported. Use org.copperengine.core.audit.ScottyAuditTrailQueryEngine instead"); - } - - public String getMessageString(long id) { - if (messagePostProcessor == null) { - throw new NullPointerException("Message Post Processor is not set. use byte[] getMessage(long id) method or set Message Post Processor"); - } - - String customSelect = "select LONG_MESSAGE from COP_AUDIT_TRAIL_EVENT where SEQ_ID = ? "; - ResultSetExtractor rse = new ResultSetExtractor() { - - @Override - public String extractData(ResultSet rs) throws SQLException, - DataAccessException { - rs.next(); - Clob message = rs.getClob("LONG_MESSAGE"); - - if ((int) message.length() == 0) { - return null; - } - - return messagePostProcessor.deserialize(message.getSubString(1, (int) message.length())); - } - }; - - JdbcTemplate jdbcTemplate = getJdbcTemplate(); - - return (jdbcTemplate != null) ? jdbcTemplate.query(customSelect, rse, new Object[] { id }) : null; - } - - private byte[] convertToArray(InputStream messageStream) { - if (messageStream == null) { - return new byte[0]; - } - - byte[] bytes = new byte[1024]; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int read = 0; - int off = 0; - try { - while ((read = messageStream.read(bytes)) > 0) { - out.write(bytes, off, read); - off += read; - } - messageStream.close(); - return out.toByteArray(); - } catch (IOException e) { - } - return null; - } - - public MessagePostProcessor getMessagePostProcessor() { - return messagePostProcessor; - } - - public void setMessagePostProcessor(MessagePostProcessor messagePostProcessor) { - this.messagePostProcessor = messagePostProcessor; - } -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/SpringTxnAuditTrail.java b/projects/copper-spring/src/main/java/org/copperengine/spring/audit/SpringTxnAuditTrail.java deleted file mode 100644 index da06e937..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/SpringTxnAuditTrail.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2015 SCOOP Software GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.copperengine.spring.audit; - -import java.sql.Connection; -import java.util.Arrays; -import java.util.Collection; - -import org.copperengine.core.CopperRuntimeException; -import org.copperengine.core.audit.AbstractAuditTrail; -import org.copperengine.core.audit.AuditTrailEvent; -import org.copperengine.core.audit.BatchInsertIntoAutoTrail.Command; -import org.copperengine.core.audit.BatchInsertIntoAutoTrail.Executor; -import org.copperengine.core.batcher.BatchCommand; -import org.copperengine.core.batcher.NullCallback; -import org.copperengine.spring.SpringTransaction; -import org.slf4j.Logger; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.DefaultTransactionDefinition; - -public class SpringTxnAuditTrail extends AbstractAuditTrail { - - private static final Logger logger = org.slf4j.LoggerFactory.getLogger(SpringTxnAuditTrail.class); - - private PlatformTransactionManager transactionManager; - - public void setTransactionManager(PlatformTransactionManager transactionManager) { - this.transactionManager = transactionManager; - } - - @Override - public void synchLog(final AuditTrailEvent e) { - if (isEnabled(e.getLogLevel())) { - logger.debug("doLog({})", e); - e.setMessage(messagePostProcessor.serialize(e.getMessage())); - try { - new SpringTransaction() { - @Override - protected void execute(Connection con) throws Exception { - @SuppressWarnings("unchecked") - BatchCommand cmd = createBatchCommand(e, true, NullCallback.instance); - Collection> cmdList = Arrays.>asList(cmd); - cmd.executor().doExec(cmdList, con); - } - }.run(transactionManager, getDataSource(), createTransactionDefinition()); - } catch (RuntimeException ex) { - throw ex; - } catch (Exception ex) { - throw new CopperRuntimeException(ex); - } - } - } - - protected TransactionDefinition createTransactionDefinition() { - return new DefaultTransactionDefinition(); - } - -} diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/package.html b/projects/copper-spring/src/main/java/org/copperengine/spring/audit/package.html deleted file mode 100644 index 54aac1d8..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/audit/package.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - -Offers high speed audit trail logging - - diff --git a/projects/copper-spring/src/main/java/org/copperengine/spring/package.html b/projects/copper-spring/src/main/java/org/copperengine/spring/package.html deleted file mode 100644 index a8fcfd7a..00000000 --- a/projects/copper-spring/src/main/java/org/copperengine/spring/package.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - -Glue code to connect COPPER and Spring - - diff --git a/settings.gradle b/settings.gradle index f843c5ce..69fd5b53 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,4 @@ include ":projects:copper-coreengine", ":projects:copper-ext", -":projects:copper-cassandra:cassandra-storage", -":projects:copper-cassandra:cassandra-loadtest", -":projects:copper-jmx-interface", -":projects:copper-spring", -":projects:copper-regtest", -":projects:copper-performance-test" +":projects:copper-jmx-interface", +":projects:copper-regtest" \ No newline at end of file From 5236d9b34278e3696bfd27596a7d174799a756ba Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 11:28:21 +0200 Subject: [PATCH 03/26] Build for keymaster --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5a0387d..0064d2eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build and upload on: push: branches: - - master + - keymaster pull_request: types: [ opened, synchronize, reopened ] From 34d4954577b80c2c10a1bd870ff8fabd438845a3 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 11:31:45 +0200 Subject: [PATCH 04/26] Remove publishing --- .github/workflows/build.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0064d2eb..5ff2dba7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,17 +10,9 @@ on: jobs: build: runs-on: ubuntu-latest - env: - SECRING_HEX: ${{ secrets.SECRING_HEX }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - NEXUS_USER: ${{ secrets.NEXUS_USER }} - NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} steps: - name: Git checkout uses: actions/checkout@v4 - - name: Create secring.gpg - run: echo $SECRING_HEX > secring.gpg.hex && xxd -p -r secring.gpg.hex > secring.gpg - name: Configure Gradle cache uses: actions/cache@v4 with: @@ -36,13 +28,4 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build - - name: Upload archives - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - run: ./gradlew --no-daemon --no-parallel --info -Psigning.secretKeyRingFile=$PWD/secring.gpg -Psigning.keyId=$SIGNING_KEY_ID -Psigning.password=$SIGNING_PASSWORD -PnexusUsername=$NEXUS_USER -PnexusPassword=$NEXUS_PASSWORD -Poss-releases.username=$NEXUS_USER -Poss-releases.password=$NEXUS_PASSWORD --full-stacktrace uploadArchives - - name: Sleep for 120 seconds - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - run: sleep 120 - - name: Nexus staging release - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - run: ./gradlew --no-daemon --no-parallel --info -Psigning.secretKeyRingFile=$PWD/secring.gpg -Psigning.keyId=$SIGNING_KEY_ID -Psigning.password=$SIGNING_PASSWORD -PnexusUsername=$NEXUS_USER -PnexusPassword=$NEXUS_PASSWORD -Poss-releases.username=$NEXUS_USER -Poss-releases.password=$NEXUS_PASSWORD --full-stacktrace nexusStagingRelease + run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build \ No newline at end of file From f1fa08fc056f335cacfa4c5df695c2c968c09d7d Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 13:30:38 +0200 Subject: [PATCH 05/26] Remove comments and deprecated usage --- build.gradle | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/build.gradle b/build.gradle index 8217d652..d631aa77 100644 --- a/build.gradle +++ b/build.gradle @@ -74,12 +74,12 @@ configure(properSubprojects) { targetCompatibility = 1.8 task createSourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task createJavadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -88,27 +88,6 @@ configure(properSubprojects) { archives createJavadocJar } -// spotbugs { -// toolVersion = '3.1.3' -// sourceSets = (javaVersion >= 10) ? [] : [project.sourceSets.main]// check only main classes, neither tests nor example workflow classes -// excludeFilter = file("$rootDir/common/findbugs-exclude-filter.xml") -// effort = "max" -// ignoreFailures = true -// } -// -// spotbugsMain { -// reports { -// // Unfortunately FindBugs cannot emit both XML and HTML report simultanously, so by default we emit HTML only. -// // We emit XML only when -PfindbugsXmlReportEnabled=true, e.g. during Jenkins build -// def findbugsXmlReportEnabled = project.hasProperty('findbugsXmlReportEnabled') && project.property('findbugsXmlReportEnabled') -// xml.enabled = findbugsXmlReportEnabled -// html.enabled = !findbugsXmlReportEnabled -// } -// } -// -// spotbugsTest.enabled = false - - if (!project.getName().contains('orch-interfaces')) { apply plugin: 'com.github.hierynomus.license' license { From c21665ab23dbb0486c86d9a2e63d25bb3f3bc045 Mon Sep 17 00:00:00 2001 From: Wolf Sluyterman van Langeweyde Date: Mon, 15 Jul 2024 20:04:10 +0200 Subject: [PATCH 06/26] Use snakeyaml 1.33 again --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ee4feea5..e225ef3c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ springBatchVersion = 4.3.10 c3p0Version = 0.10.0 guavaVersion = 31.0.1-jre jacksonVersion = 2.17.0 -snakeyamlVersion = 2.2 +snakeyamlVersion = 1.33 commonsIoVersion = 2.16.1 commonsLangVersion = 2.6 From 502b7db872191784d81c40e5f12bc4e97dc8e358 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sat, 20 Jul 2024 13:14:19 +0200 Subject: [PATCH 07/26] Using newest existing gradle-nexus-plugin 0.3, as 0.7 is lost --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fab88836..a637cd5f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { // The nexus plugin makes uploading to Sonatype Nexus repository easier. // See https://github.com/bmuschko/gradle-nexus-plugin for documentation - classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.7' + classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.3' // The nexus-workflow plugin automates the staging/promotion/release process on Sonatype OSS // by providing the task 'nexusStagingRelease'. // See https://github.com/adaptivecomputing/plugins-gradle/tree/master/nexus-workflow From 8498bc5d020b8ba39a1f69dcde63eb8cc18f2cb3 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sat, 20 Jul 2024 13:25:13 +0200 Subject: [PATCH 08/26] Add tests for YamlSerializer --- .gitignore | 1 + .../ext/persistent/YamlSerializer.java | 13 ++- .../ext/persistent/YamlSerializerTest.java | 88 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 projects/copper-ext/src/test/java/org/copperengine/ext/persistent/YamlSerializerTest.java diff --git a/.gitignore b/.gitignore index b6efc011..8bf89eac 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ out/ # Copper specific user setting files projects/copper-regtest/src/test/resources/regtest.*.properties !projects/copper-regtest/src/test/resources/regtest.default.properties +/projects/copper-ext/wf-work/ diff --git a/projects/copper-ext/src/main/java/org/copperengine/ext/persistent/YamlSerializer.java b/projects/copper-ext/src/main/java/org/copperengine/ext/persistent/YamlSerializer.java index 7c3705c1..117bcf23 100644 --- a/projects/copper-ext/src/main/java/org/copperengine/ext/persistent/YamlSerializer.java +++ b/projects/copper-ext/src/main/java/org/copperengine/ext/persistent/YamlSerializer.java @@ -57,12 +57,21 @@ protected Yaml initialYaml() { @Override protected String serializeData(Workflow o) throws IOException { - return yaml.get().dump(o.getData()); + return serialize(o.getData()); } @Override protected Object deserializeData(SerializedWorkflow sw) throws Exception { - return yaml.get().load(sw.getData()); + return deserialize(sw.getData()); + } + + + String serialize(final Object workflowData) { + return yaml.get().dump(workflowData); + } + + Object deserialize(String workflowData) { + return yaml.get().load(workflowData); } } diff --git a/projects/copper-ext/src/test/java/org/copperengine/ext/persistent/YamlSerializerTest.java b/projects/copper-ext/src/test/java/org/copperengine/ext/persistent/YamlSerializerTest.java new file mode 100644 index 00000000..6e5e1d9c --- /dev/null +++ b/projects/copper-ext/src/test/java/org/copperengine/ext/persistent/YamlSerializerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2024 SCOOP Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.copperengine.ext.persistent; + +import org.junit.Test; + +import java.io.Serializable; +import java.util.Objects; + +import static junit.framework.TestCase.assertEquals; + +public class YamlSerializerTest { + + private final YamlSerializer serializer = new YamlSerializer(); + + public static class User implements Serializable { + @SuppressWarnings("unused") + User() {} + + User(String name) { + this.name = name; + } + public String name; + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof User)) return false; + final User user = (User) o; + return Objects.equals(name, user.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + } + + @Test + public void string() throws Exception { + final String workflowData = "Test"; + + + final String serialized = serializer.serialize(workflowData); + final Object deserialized = serializer.deserialize(serialized); + + + assertEquals(workflowData, deserialized); + } + + @Test + public void userClass() throws Exception { + final User workflowData = new User( "Wolf"); + + + final String serialized = serializer.serialize(workflowData); + final Object deserialized = serializer.deserialize(serialized); + + + assertEquals(workflowData, deserialized); + } + + @Test + public void compatibilityUserClass() throws Exception { + final User workflowData = new User( "Wolf"); + + + // taken from snakeyaml 1.33 + final String serialized = "!!org.copperengine.ext.persistent.YamlSerializerTest$User {name: Wolf}"; + final Object deserialized = serializer.deserialize(serialized); + + + assertEquals(workflowData, deserialized); + } +} \ No newline at end of file From 9efe9b2844e38e01d675f8c2c08b5d3fcaa81411 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sat, 20 Jul 2024 13:33:07 +0200 Subject: [PATCH 09/26] Longer timeout in changeGitRepositoryDirTest and change2BranchesTest --- .../ext/wfrepo/git/GitWorkflowRepositoryTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java index 0cb80421..538b19b8 100755 --- a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java +++ b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java @@ -111,13 +111,13 @@ public void defaultBranchTest() throws Exception { @Test public void change2BranchesTest() throws CopperException, InterruptedException, IOException, GitAPIException { wfRepo.setBranch("1.0"); - LockSupport.parkNanos(1_000_000_000 + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh engine.run("Workflow1", "foo"); String result1 = (String) channel.wait("correlationId", 1000, TimeUnit.MILLISECONDS); assertEquals("V1.0", result1); wfRepo.setBranch("2.0"); - LockSupport.parkNanos(1_000_000_000 + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh engine.run("Workflow1", "foo"); String result2 = (String) channel.wait("correlationId", 1000, TimeUnit.MILLISECONDS); assertEquals("V2.0", result2); @@ -177,7 +177,7 @@ public void changeGitRepositoryDirTest() throws Exception { List sourceDirs = new ArrayList(1); sourceDirs.add(0, WORK_DIR + "/wf-source2"); wfRepo.setSourceDirs(sourceDirs); - LockSupport.parkNanos(1_000_000_000 + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh change2BranchesTest(); // should run, because working classes are not overwritten (with empty configuration) by copper } From 65ec49d1165099d2f325a64d63e82a3aab3a2fae Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sat, 20 Jul 2024 16:15:50 +0200 Subject: [PATCH 10/26] Try com.bmuschko instead of org.gradle.api.plugins --- build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index a637cd5f..6b801d48 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { // The nexus plugin makes uploading to Sonatype Nexus repository easier. // See https://github.com/bmuschko/gradle-nexus-plugin for documentation - classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.3' + classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' // The nexus-workflow plugin automates the staging/promotion/release process on Sonatype OSS // by providing the task 'nexusStagingRelease'. // See https://github.com/adaptivecomputing/plugins-gradle/tree/master/nexus-workflow @@ -192,9 +192,8 @@ configure(properSubprojects) { if (!project.sourceCompatibility.java9Compatible && project.hasProperty('nexusUsername')) { // the nexus plugin makes uploading to Sonatype Nexus repository easier // see https://github.com/bmuschko/gradle-nexus-plugin for documentation - apply plugin: 'nexus' + apply plugin: 'com.bmuschko.nexus' nexus { - attachTests = true sign = true } From 2d4d7e92d600422acd807a26e1e46e79f45ab035 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 14:50:42 +0200 Subject: [PATCH 11/26] Java 21 --- .github/workflows/build.yml | 4 +- build.gradle | 208 ++---------------- gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 53 +++-- gradlew.bat | 43 ++-- .../src/main/java/module-info.java | 1 - .../src/main/java/module-info.java | 1 - 8 files changed, 77 insertions(+), 235 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ff2dba7..a9aa34a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,11 +20,11 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: | ${{ runner.os }}-gradle- - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: zulu - java-version: 11 + java-version: 21 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle diff --git a/build.gradle b/build.gradle index d631aa77..4bf32a94 100644 --- a/build.gradle +++ b/build.gradle @@ -1,49 +1,11 @@ -import groovy.xml.QName - import java.nio.charset.StandardCharsets -buildscript { - repositories { - gradlePluginPortal() // needed for gradle-nexus-plugin - mavenCentral() // needed for nexus-workflow plugin - maven { // needed for license-gradle-plugin - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - // The nexus plugin makes uploading to Sonatype Nexus repository easier. - // See https://github.com/bmuschko/gradle-nexus-plugin for documentation - classpath 'org.gradle.api.plugins:gradle-nexus-plugin:0.7' - // The nexus-workflow plugin automates the staging/promotion/release process on Sonatype OSS - // by providing the task 'nexusStagingRelease'. - // See https://github.com/adaptivecomputing/plugins-gradle/tree/master/nexus-workflow - // and http://stackoverflow.com/questions/20432907/automated-release-to-maven-central-with-gradle - classpath 'com.adaptc.gradle:nexus-workflow:0.6' - // the license plugin, see https://github.com/hierynomus/license-gradle-plugin - classpath 'gradle.plugin.nl.javadude.gradle.plugins:license-gradle-plugin:0.13.0' - // the spotbugs plugin, see https://plugins.gradle.org/plugin/com.github.spotbugs - // DISABLED: spotbugs is currently not compatible with Gradle 5 - // classpath "gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.8" - - // Plugin to create modular jars that target a Java release before 9 (https://github.com/beryx/badass-jar-plugin) - classpath "gradle.plugin.org.beryx:badass-jar:1.2.0" - - // Plugin for loading project properties from gradle-local.properties - classpath "net.saliman:gradle-properties-plugin:1.5.1" - - // OSGI plugin - classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0" - } +plugins { + id("com.github.ben-manes.versions") version "0.51.0" + id("com.github.hierynomus.license-base") version "0.16.1" } - allprojects { - apply plugin: 'project-report' - apply plugin: "net.saliman.properties" - - // DISABLED: spotbugs is currently not compatible with Gradle 5 -// apply plugin: "com.github.spotbugs" - group = "org.copper-engine" repositories { @@ -61,17 +23,10 @@ configure(properSubprojects) { println "configuring java module " + project.path apply plugin: 'java-library' - apply plugin: 'biz.aQute.bnd.builder' - - apply plugin: "org.beryx.jar" + apply plugin: 'maven-publish' - compileJava.options.encoding = StandardCharsets.UTF_8 - compileTestJava.options.encoding = StandardCharsets.UTF_8 - - apply plugin: 'maven' - - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 21 + targetCompatibility = 21 task createSourcesJar(type: Jar, dependsOn: classes) { archiveClassifier = 'sources' @@ -88,63 +43,21 @@ configure(properSubprojects) { archives createJavadocJar } - if (!project.getName().contains('orch-interfaces')) { - apply plugin: 'com.github.hierynomus.license' - license { - // verify that every java file has our Apache License header; fail build if header is missing - header file("$rootDir/common/apache-license-file.txt") - skipExistingHeaders true - ignoreFailures true - } + apply plugin: 'com.github.hierynomus.license' + license { + // verify that every java file has our Apache License header; fail build if header is missing + header file("$rootDir/common/apache-license-file.txt") + skipExistingHeaders true + ignoreFailures true } - apply plugin: 'eclipse' - eclipse { - classpath { - defaultOutputDir = file('build') - file { - //exclude slf4f log binding from export - withXml { - Node root = it.asNode() - NodeList nodeList = root.getAt(new QName('classpathentry')) - nodeList.each { Node classpathentry -> - if (classpathentry.attributes().path.contains('slf4j-log4j12')) { - classpathentry.attributes().remove('exported') - } - } - } - } - } - jdt { - file { - // add our code style settings to every eclipse project - withProperties { properties -> - def codestyle = new XmlParser().parse(file("$rootDir/common/eclipse-codestyle.xml")) - codestyle.profile[0].setting.each { - properties.put(it.'@id', it.'@value') - } - } - whenMerged { - def uiprops = new Properties(); - uiprops.put('eclipse.preferences.version', '1') - uiprops.put('formatter_profile', '_SCOOP-CodeStyle') - uiprops.put('formatter_settings_version', '12') - uiprops.store(file("$projectDir/.settings/org.eclipse.jdt.ui.prefs").newWriter(), "generated by build.gradle") - } - } - } - } - // be sure to always regenerate eclipse files, because default behavior is merging into existing files - tasks.eclipse.dependsOn cleanEclipse - eclipse.classpath.defaultOutputDir = new File("$buildDir/classes/main") - dependencies { implementation("com.github.javaparser:javaparser-symbol-solver-core:$javaparserVersion") { exclude module: 'javaparser-symbol-solver-model' } - implementation "org.slf4j:slf4j-api:$slf4jVersion" + implementation "org.slf4j:slf4j-api:$slf4jVersion" testImplementation("junit:junit:$junitVersion") { exclude module: 'hamcrest-core' } @@ -152,13 +65,10 @@ configure(properSubprojects) { testImplementation "net.bytebuddy:byte-buddy:$byteBuddyVersion" testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" - -// spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.7.1' } jar { manifest.attributes provider: 'gradle' - multiRelease = false } javadoc { @@ -168,50 +78,6 @@ configure(properSubprojects) { exclude '**/module-info.java' } - if (!project.sourceCompatibility.java9Compatible && project.hasProperty('nexusUsername')) { - // the nexus plugin makes uploading to Sonatype Nexus repository easier - // see https://github.com/bmuschko/gradle-nexus-plugin for documentation - apply plugin: 'nexus' - nexus { - attachTests = true - sign = true - } - - modifyPom { - project { - name = 'COPPER high-performance workflow engine' - packaging = 'jar' - description = 'COPPER is an open-source, powerful, light-weight, and easily configurable workflow engine. The power of COPPER is that it uses Java as a description language for workflows.' - url 'http://copper-engine.org/' - - scm { - url 'https://github.com/copper-engine/copper-engine' - connection 'scm:git@github.com:copper-engine/copper-engine.git' - } - - licenses { - license { - name = 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' - } - } - - developers { - developer { - id 'copper-team' - name = 'Copper Engine Development Team' - roles { - role 'architect' - role 'developer' - role 'tester' - } - } - } - } - } - } - test { testLogging { exceptionFormat = 'full' @@ -219,42 +85,6 @@ configure(properSubprojects) { } } -configure(rootProject) { - // The nexus-workflow plugin automates the staging/promotion/release process on Sonatype OSS - // by providing the task 'nexusStagingRelease'. We perform "nexusStagingRelease" only if the current - // version is not a SNAPSHOT release. - apply plugin: 'nexus-workflow' - tasks.nexusStagingRelease.onlyIf { !version.endsWith("-SNAPSHOT") } - - // task to create source distribution containing all source files - // note: this task won't run automatically; it must be called explicitely - task fullSourcesJar(type: Jar) { - classifier = 'sources-full' - from project.rootDir - exclude '**/build/' - exclude '**/classes/' - exclude '**/generated/' - exclude '.gradle/' - exclude 'tmp/' - exclude '.idea' - exclude '**/*.iml' - exclude '**/*.log' - exclude '**/*.classpath' - exclude '**/*.project' - exclude '**/*.settings/' - } - - // copy Intellij Codestyle settings each time 'gradle assemble' or 'gradle build' is performed on the root project - task ideaCodeStyleSetup(type: Copy) { - from 'common/intellij-codestyle.xml' - into '.idea/' - rename '.+', 'codeStyleSettings.xml' - } - ideaCodeStyleSetup.onlyIf { file(".idea").exists() } - assemble.dependsOn ideaCodeStyleSetup -} - - project(':projects:copper-jmx-interface') { ext.moduleName = 'org.copperengine.management' @@ -274,7 +104,7 @@ project(':projects:copper-regtest') { sourceSets.test.resources.srcDirs += sourceSets.workflow.srcDir tasks.testClasses.doLast { - if(project.sourceCompatibility.java9Compatible) { + if (project.sourceCompatibility.java9Compatible) { copy { from sourceSets.test.resources.srcDirs into "$buildDir/classes/java/test" @@ -311,7 +141,7 @@ project(':projects:copper-coreengine') { dependencies { api project(':projects:copper-jmx-interface') - compile "org.slf4j:slf4j-api:$slf4jVersion" + implementation("org.slf4j:slf4j-api:$slf4jVersion") // asm implementation "org.ow2.asm:asm:$asmVersion" @@ -322,7 +152,7 @@ project(':projects:copper-coreengine') { } task scriptsZip(type: Zip) { - classifier = 'scripts' + archiveClassifier = 'scripts' from file("src/main/database") into 'scripts/sql' } @@ -338,7 +168,7 @@ project(':projects:copper-ext') { dependencies { implementation project(':projects:copper-coreengine') - implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" + implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" implementation "org.ow2.asm:asm:$asmVersion" implementation "org.ow2.asm:asm-tree:$asmVersion" implementation "commons-io:commons-io:$commonsIoVersion" @@ -349,8 +179,4 @@ project(':projects:copper-ext') { //testImplementation 'org.apache.logging.log4j:log4j-core:2.+' //testImplementation 'org.slf4j:slf4j-log4j12:2.+' } -} - -subprojects { - task allDeps(type: DependencyReportTask) {} } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 91ca28c8b802289c3a438766657a5e98f20eff03..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 54413 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/projects/copper-coreengine/src/main/java/module-info.java b/projects/copper-coreengine/src/main/java/module-info.java index 6d97fcec..df123e2b 100644 --- a/projects/copper-coreengine/src/main/java/module-info.java +++ b/projects/copper-coreengine/src/main/java/module-info.java @@ -9,7 +9,6 @@ requires org.objectweb.asm.util; requires java.desktop; requires java.compiler; - requires java.xml.bind; uses javax.tools.JavaCompiler; diff --git a/projects/copper-regtest/src/main/java/module-info.java b/projects/copper-regtest/src/main/java/module-info.java index d28c5109..e8e7fd16 100644 --- a/projects/copper-regtest/src/main/java/module-info.java +++ b/projects/copper-regtest/src/main/java/module-info.java @@ -4,7 +4,6 @@ requires org.copperengine.core; requires org.copperengine.ext; - requires org.copperengine.spring; requires org.slf4j; From 5da0c6317dd6d83872564a7bb24e0afd6782a1ff Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 23:36:43 +0200 Subject: [PATCH 12/26] Longer timeout in changeGitRepositoryDirTest --- .../copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java index 0cb80421..fa07c8b0 100755 --- a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java +++ b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java @@ -177,7 +177,7 @@ public void changeGitRepositoryDirTest() throws Exception { List sourceDirs = new ArrayList(1); sourceDirs.add(0, WORK_DIR + "/wf-source2"); wfRepo.setSourceDirs(sourceDirs); - LockSupport.parkNanos(1_000_000_000 + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 3_000_000L); // wait for workflow refresh change2BranchesTest(); // should run, because working classes are not overwritten (with empty configuration) by copper } From b62da7378181476372dc10a2a0fe8738296252c9 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 6 Jun 2024 23:39:45 +0200 Subject: [PATCH 13/26] Remove deprecated usage of "new Long" --- .../copperengine/core/tranzient/DefaultTimeoutManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/DefaultTimeoutManager.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/DefaultTimeoutManager.java index b780d5f3..a8aca5b5 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/DefaultTimeoutManager.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/DefaultTimeoutManager.java @@ -124,7 +124,7 @@ public void run() { @Override public void registerTimeout(long _timeoutTS, String correlationId) { - Long timeoutTS = new Long(processSlot(_timeoutTS)); + Long timeoutTS = Long.valueOf(processSlot(_timeoutTS)); if (logger.isDebugEnabled()) { long currentTime = System.currentTimeMillis(); logger.debug("currentTime=" + currentTime); @@ -145,7 +145,7 @@ public void registerTimeout(long _timeoutTS, String correlationId) { @Override public void registerTimeout(long _timeoutTS, List correlationIds) { - Long timeoutTS = new Long(processSlot(_timeoutTS)); + Long timeoutTS = Long.valueOf(processSlot(_timeoutTS)); if (logger.isDebugEnabled()) { long currentTime = System.currentTimeMillis(); logger.debug("currentTime=" + currentTime); From b1ccbdd04eb65774b72f73741ad2ddb145d2111a Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Fri, 13 Dec 2024 13:40:58 +0100 Subject: [PATCH 14/26] Add signed publishing for group io.github.keymaster65 --- build.gradle | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/build.gradle b/build.gradle index 4bf32a94..68c72413 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ configure(properSubprojects) { apply plugin: 'java-library' apply plugin: 'maven-publish' + apply plugin: 'signing' sourceCompatibility = 21 targetCompatibility = 21 @@ -51,6 +52,64 @@ configure(properSubprojects) { ignoreFailures true } + apply plugin: 'signing' + signing { + sign configurations.archives + } + + publishing { + publications { + signing.sign( + create("library", MavenPublication) { + from components.java + pom { + name = 'COPPER high-performance workflow engine' + group = 'io.github.keymaster65' + packaging = 'jar' + description = 'COPPER is an open-source, powerful, light-weight, and easily configurable workflow engine. The power of COPPER is that it uses Java as a description language for workflows.' + url = 'http://copper-engine.org/' + + scm { + url = 'https://github.com/copper-engine/copper-engine' + connection = 'scm:git@github.com:copper-engine/copper-engine.git' + } + + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution = 'repo' + } + } + + developers { + developer { + id = 'copper-team' + name = 'Copper Engine Development Team' + } + } + } + } + ) + } + } + + publishing { + repositories { + maven { + credentials { + username project.hasProperty('SONA_TOKEN_USERNAME') ? project.SONA_TOKEN_USERNAME : '' + password project.hasProperty('SONA_TOKEN_PASSWORD') ? project.SONA_TOKEN_PASSWORD : '' + } + if (version.toString().endsWith('-SNAPSHOT')) { + url = uri('https://oss.sonatype.org/content/repositories/snapshots/') + } else { + url = uri('https://oss.sonatype.org/service/local/staging/deploy/maven2/') + } + } + } + } + dependencies { implementation("com.github.javaparser:javaparser-symbol-solver-core:$javaparserVersion") { From c67c96a502f056d0cf69a57e7060be28fbc2deea Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Fri, 13 Dec 2024 15:07:31 +0100 Subject: [PATCH 15/26] No signArchives --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9aa34a5..4b2f6d3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,4 +28,4 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build \ No newline at end of file + run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build -x signArchives \ No newline at end of file From 9bbee67585d96583b6d0764a68c1f780277628b5 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Fri, 13 Dec 2024 15:10:21 +0100 Subject: [PATCH 16/26] No chmod on gradlew --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b2f6d3a..798295a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,5 @@ jobs: with: distribution: zulu java-version: 21 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - name: Build with Gradle run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build -x signArchives \ No newline at end of file From 004e4eee67fe7c72947547a2ce64711f351fb8e1 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sun, 15 Dec 2024 11:57:41 +0100 Subject: [PATCH 17/26] Refinement for "Update to Java 21" as COPPER 21.0.0 --- WHATSNEW.txt | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WHATSNEW.txt b/WHATSNEW.txt index 35d264db..39e09306 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -1,4 +1,4 @@ -COPPER 6.0.0 +COPPER 21.0.0 ============ - Breaking: Update to Java 21 diff --git a/gradle.properties b/gradle.properties index 48a5a531..a8e5d4ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=6.0.0-SNAPSHOT +version=21.0.0-SNAPSHOT org.gradle.console=plain From 5ae5e1242486e7c341a18190e7fc7f2fbdcfcbdc Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sun, 15 Dec 2024 12:19:55 +0100 Subject: [PATCH 18/26] Longer delays in change2BranchesTest --- .../ext/wfrepo/git/GitWorkflowRepositoryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java index 2e9d6e74..ca6acf97 100755 --- a/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java +++ b/projects/copper-ext/src/test/java/org/copperengine/ext/wfrepo/git/GitWorkflowRepositoryTest.java @@ -111,13 +111,13 @@ public void defaultBranchTest() throws Exception { @Test public void change2BranchesTest() throws CopperException, InterruptedException, IOException, GitAPIException { wfRepo.setBranch("1.0"); - LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(5_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh engine.run("Workflow1", "foo"); String result1 = (String) channel.wait("correlationId", 1000, TimeUnit.MILLISECONDS); assertEquals("V1.0", result1); wfRepo.setBranch("2.0"); - LockSupport.parkNanos(3_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh + LockSupport.parkNanos(5_000_000_000L + CHECK_INTERVAL_M_SEC * 1_000_000); // wait for workflow refresh engine.run("Workflow1", "foo"); String result2 = (String) channel.wait("correlationId", 1000, TimeUnit.MILLISECONDS); assertEquals("V2.0", result2); From 24485272236b2ba44deb258668f032631e4af1ef Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sun, 15 Dec 2024 12:21:09 +0100 Subject: [PATCH 19/26] Add Exclude from publishing --- WHATSNEW.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/WHATSNEW.txt b/WHATSNEW.txt index 39e09306..fa45c5c9 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -1,6 +1,7 @@ COPPER 21.0.0 ============ - Breaking: Update to Java 21 +- Breaking: Exclude from publishing: copper-spring, copper-performance-test and copper-cassandra COPPER 5.5.2 ============ From 724f9aad9c65c800f7a7f8e1103a8307b37e9a42 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Sun, 15 Dec 2024 12:32:50 +0100 Subject: [PATCH 20/26] Update jgit 6.9.0.202403050737-r to 7.1.0.202411261347-r and Update gradle 8.8 to 8.11.1 --- WHATSNEW.txt | 2 + gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 285 ++++++++++++++--------- gradlew.bat | 37 +-- 6 files changed, 203 insertions(+), 127 deletions(-) diff --git a/WHATSNEW.txt b/WHATSNEW.txt index fa45c5c9..e50f805d 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -2,6 +2,8 @@ COPPER 21.0.0 ============ - Breaking: Update to Java 21 - Breaking: Exclude from publishing: copper-spring, copper-performance-test and copper-cassandra +- Breaking: Update jgit 6.9.0.202403050737-r to 7.1.0.202411261347-r +- Maintenance: Update gradle 8.8 to 8.11.1 COPPER 5.5.2 ============ diff --git a/gradle.properties b/gradle.properties index 9211af86..c6e88d03 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ mockitoVersion = 5.11.0 byteBuddyVersion = 1.14.13 hamcrestVersion = 2.2 -jgitVersion = 6.9.0.202403050737-r +jgitVersion = 7.1.0.202411261347-r derbyVersion = 10.13.1.1 mysqlVersion = 5.1.39 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch literal 43583 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d184210..e2847c82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,69 +15,104 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +122,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 24799a7ca5c0caffa2c443b1abbd1f3f84e2f4b3 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Mon, 16 Dec 2024 14:29:17 +0100 Subject: [PATCH 21/26] Migrate groovy to kotlin --- .github/workflows/build.yml | 2 +- build.gradle | 241 ------------------ build.gradle.kts | 127 +++++++++ gradle.properties | 36 +-- projects/copper-coreengine/build.gradle.kts | 27 ++ projects/copper-ext/build.gradle.kts | 14 + .../copper-jmx-interface/build.gradle.kts | 1 + projects/copper-regtest/build.gradle.kts | 29 +++ settings.gradle | 4 - settings.gradle.kts | 6 + 10 files changed, 207 insertions(+), 280 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 projects/copper-coreengine/build.gradle.kts create mode 100644 projects/copper-ext/build.gradle.kts create mode 100644 projects/copper-jmx-interface/build.gradle.kts create mode 100644 projects/copper-regtest/build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 798295a5..abf96fe1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,4 +26,4 @@ jobs: distribution: zulu java-version: 21 - name: Build with Gradle - run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build -x signArchives \ No newline at end of file + run: ./gradlew --no-daemon --no-parallel --rerun-tasks --info --full-stacktrace build \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 68c72413..00000000 --- a/build.gradle +++ /dev/null @@ -1,241 +0,0 @@ -import java.nio.charset.StandardCharsets - -plugins { - id("com.github.ben-manes.versions") version "0.51.0" - id("com.github.hierynomus.license-base") version "0.16.1" -} - -allprojects { - group = "org.copper-engine" - - repositories { - mavenCentral() - } -} - -def getProperSubprojects() { - subprojects.findAll { - new File(it.projectDir, 'src/main/java').directory - } -} - -configure(properSubprojects) { - println "configuring java module " + project.path - - apply plugin: 'java-library' - apply plugin: 'maven-publish' - apply plugin: 'signing' - - sourceCompatibility = 21 - targetCompatibility = 21 - - task createSourcesJar(type: Jar, dependsOn: classes) { - archiveClassifier = 'sources' - from sourceSets.main.allSource - } - - task createJavadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier = 'javadoc' - from javadoc.destinationDir - } - - artifacts { - archives createSourcesJar - archives createJavadocJar - } - - apply plugin: 'com.github.hierynomus.license' - license { - // verify that every java file has our Apache License header; fail build if header is missing - header file("$rootDir/common/apache-license-file.txt") - skipExistingHeaders true - ignoreFailures true - } - - apply plugin: 'signing' - signing { - sign configurations.archives - } - - publishing { - publications { - signing.sign( - create("library", MavenPublication) { - from components.java - pom { - name = 'COPPER high-performance workflow engine' - group = 'io.github.keymaster65' - packaging = 'jar' - description = 'COPPER is an open-source, powerful, light-weight, and easily configurable workflow engine. The power of COPPER is that it uses Java as a description language for workflows.' - url = 'http://copper-engine.org/' - - scm { - url = 'https://github.com/copper-engine/copper-engine' - connection = 'scm:git@github.com:copper-engine/copper-engine.git' - } - - licenses { - license { - name = 'The Apache Software License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution = 'repo' - } - } - - developers { - developer { - id = 'copper-team' - name = 'Copper Engine Development Team' - } - } - } - } - ) - } - } - - publishing { - repositories { - maven { - credentials { - username project.hasProperty('SONA_TOKEN_USERNAME') ? project.SONA_TOKEN_USERNAME : '' - password project.hasProperty('SONA_TOKEN_PASSWORD') ? project.SONA_TOKEN_PASSWORD : '' - } - if (version.toString().endsWith('-SNAPSHOT')) { - url = uri('https://oss.sonatype.org/content/repositories/snapshots/') - } else { - url = uri('https://oss.sonatype.org/service/local/staging/deploy/maven2/') - } - } - } - } - - dependencies { - - implementation("com.github.javaparser:javaparser-symbol-solver-core:$javaparserVersion") { - exclude module: 'javaparser-symbol-solver-model' - } - - implementation "org.slf4j:slf4j-api:$slf4jVersion" - testImplementation("junit:junit:$junitVersion") { - exclude module: 'hamcrest-core' - } - testImplementation "org.mockito:mockito-core:$mockitoVersion" - testImplementation "net.bytebuddy:byte-buddy:$byteBuddyVersion" - testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" - testImplementation "ch.qos.logback:logback-classic:$logbackVersion" - } - - jar { - manifest.attributes provider: 'gradle' - } - - javadoc { - onlyIf { !project.sourceCompatibility.java9Compatible } - options.encoding = StandardCharsets.UTF_8 - options.addBooleanOption('html5', true) - exclude '**/module-info.java' - } - - test { - testLogging { - exceptionFormat = 'full' - } - } -} - -project(':projects:copper-jmx-interface') { - ext.moduleName = 'org.copperengine.management' - - dependencies { - } -} - - -project(':projects:copper-regtest') { - ext.moduleName = 'org.copperengine.regtest' - - sourceSets { - workflow { - ext.srcDir = "$projectDir/src/workflow/java" - } - } - sourceSets.test.resources.srcDirs += sourceSets.workflow.srcDir - - tasks.testClasses.doLast { - if (project.sourceCompatibility.java9Compatible) { - copy { - from sourceSets.test.resources.srcDirs - into "$buildDir/classes/java/test" - } - } - } - - dependencies { - implementation project(':projects:copper-coreengine') - implementation project(':projects:copper-ext') - - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" - implementation "org.springframework:spring-jdbc:$springVersion" - implementation "org.springframework:spring-context:$springVersion" - implementation "org.springframework:spring-tx:$springVersion" - implementation "com.google.guava:guava:$guavaVersion" - testRuntimeOnly fileTree(dir: "$rootDir/3rdPartyLibs", include: '*.jar') - - testImplementation 'mysql:mysql-connector-java:5.1.25' - testImplementation "org.apache.derby:derby:$derbyVersion" - testImplementation "postgresql:postgresql:$postgresqlVersion" - testImplementation "com.h2database:h2:$h2Version" - testImplementation "com.mchange:c3p0:$c3p0Version" - - testImplementation "org.slf4j:slf4j-api:$slf4jVersion" - - } -} - -project(':projects:copper-coreengine') { - ext.moduleName = 'org.copperengine.core' - dependencies { - api project(':projects:copper-jmx-interface') - - implementation("org.slf4j:slf4j-api:$slf4jVersion") - - // asm - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-commons:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "org.ow2.asm:asm-util:$asmVersion" - implementation "org.ow2.asm:asm-analysis:$asmVersion" - } - - task scriptsZip(type: Zip) { - archiveClassifier = 'scripts' - from file("src/main/database") - into 'scripts/sql' - } - assemble.dependsOn scriptsZip - - artifacts { - archives scriptsZip - } -} - -project(':projects:copper-ext') { - ext.moduleName = 'org.copperengine.ext' - dependencies { - implementation project(':projects:copper-coreengine') - - implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" - implementation "org.ow2.asm:asm:$asmVersion" - implementation "org.ow2.asm:asm-tree:$asmVersion" - implementation "commons-io:commons-io:$commonsIoVersion" - implementation "com.google.guava:guava:$guavaVersion" - implementation "org.yaml:snakeyaml:$snakeyamlVersion" - - testImplementation "org.slf4j:slf4j-api:$slf4jVersion" - //testImplementation 'org.apache.logging.log4j:log4j-core:2.+' - //testImplementation 'org.slf4j:slf4j-log4j12:2.+' - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..a603276e --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,127 @@ +plugins { + id("com.github.ben-manes.versions") version "0.51.0" + id("com.github.hierynomus.license-base") version "0.16.1" + `maven-publish` + `java-library` + signing + +} + +subprojects { + apply(plugin = "java-library") + apply(plugin = "maven-publish") + apply(plugin = "signing") + + group = "org.copper-engine" + + repositories { + mavenCentral() + } + + + java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + tasks.register("createSourcesJar") { + dependsOn(tasks.classes) + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + tasks.register("createJavadocJar") { + dependsOn(tasks.javadoc) + archiveClassifier.set("javadoc") + from(tasks.javadoc.get().destinationDir) + } + + artifacts { + archives(tasks.named("createSourcesJar")) + archives(tasks.named("createJavadocJar")) + } + + apply(plugin = "com.github.hierynomus.license") + license { + header = file("$rootDir/common/apache-license-file.txt") + setSkipExistingHeaders(true) + setIgnoreFailures(true) + } + + apply(plugin = "signing") + publishing { + publications { + signing.sign( + create("library") { + from(components["java"]) + pom { + name.set("COPPER high-performance workflow engine") + groupId = "io.github.keymaster65" + packaging = "jar" + description.set("COPPER is an open-source, powerful, light-weight, and easily configurable workflow engine. The power of COPPER is that it uses Java as a description language for workflows.") + url.set("http://copper-engine.org/") + + scm { + url.set("https://github.com/copper-engine/copper-engine") + connection.set("scm:git@github.com:copper-engine/copper-engine.git") + } + + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + + developers { + developer { + id.set("copper-team") + name.set("Copper Engine Development Team") + } + } + } + } + ) + } + } + + publishing { + repositories { + maven { + credentials { + username = project.findProperty("SONA_TOKEN_USERNAME")?.toString() ?: "" + password = project.findProperty("SONA_TOKEN_PASSWORD")?.toString() ?: "" + } + url = uri( + if (version.toString().endsWith("-SNAPSHOT")) { + "https://oss.sonatype.org/content/repositories/snapshots/" + } else { + "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + } + ) + } + } + } + + dependencies { + implementation("com.github.javaparser:javaparser-symbol-solver-core:3.6.23") { + exclude(module = "javaparser-symbol-solver-model") + } + implementation("org.slf4j:slf4j-api:2.0.13") + + testImplementation("junit:junit:4.13.2") { + exclude(module = "hamcrest-core") + } + testImplementation("org.mockito:mockito-core:5.11.0") + testImplementation("net.bytebuddy:byte-buddy:1.14.13") + testImplementation("org.hamcrest:hamcrest-core:2.2") + testImplementation("ch.qos.logback:logback-classic:1.5.6") + } + + tasks.jar { + manifest { + attributes["provider"] = "gradle" + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c6e88d03..b9d4961f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,37 +1,5 @@ version=21.0.0-SNAPSHOT org.gradle.console=plain - -oss-releases.url=https://oss.sonatype.org/index.html#stagingRepositories - -asmVersion = 9.7 - -springVersion = 5.3.36 -springBatchVersion = 4.3.10 - -c3p0Version = 0.10.0 -guavaVersion = 31.0.1-jre -jacksonVersion = 2.17.0 -snakeyamlVersion = 1.33 -commonsIoVersion = 2.16.1 -commonsLangVersion = 2.6 - -slf4jVersion = 2.0.13 -logbackVersion = 1.5.6 - -junitVersion = 4.13.2 -mockitoVersion = 5.11.0 -byteBuddyVersion = 1.14.13 -hamcrestVersion = 2.2 - -jgitVersion = 7.1.0.202411261347-r - -derbyVersion = 10.13.1.1 -mysqlVersion = 5.1.39 -postgresqlVersion = 9.1-901.jdbc4 -cassandraDriverVersion = 3.10.2 -cassandraUnitVersion = 3.1.1.0 -h2Version = 1.4.193 - -javaparserVersion = 3.6.23 - +org.gradle.vfs.watch=true +oss-releases.url=https://oss.sonatype.org/index.html#stagingRepositories \ No newline at end of file diff --git a/projects/copper-coreengine/build.gradle.kts b/projects/copper-coreengine/build.gradle.kts new file mode 100644 index 00000000..31949fff --- /dev/null +++ b/projects/copper-coreengine/build.gradle.kts @@ -0,0 +1,27 @@ +ext["moduleName"] = "org.copperengine.core" + +dependencies { + api(project(":projects:copper-jmx-interface")) + + implementation("org.slf4j:slf4j-api:2.0.13") + + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-commons:9.7") + implementation("org.ow2.asm:asm-tree:9.7") + implementation("org.ow2.asm:asm-util:9.7") + implementation("org.ow2.asm:asm-analysis:9.7") +} + +tasks.register("scriptsZip") { + archiveClassifier.set("scripts") + from("src/main/database") + into("scripts/sql") +} + +tasks.assemble { + dependsOn(tasks.named("scriptsZip")) +} + +artifacts { + archives(tasks.named("scriptsZip")) +} diff --git a/projects/copper-ext/build.gradle.kts b/projects/copper-ext/build.gradle.kts new file mode 100644 index 00000000..5e7d3937 --- /dev/null +++ b/projects/copper-ext/build.gradle.kts @@ -0,0 +1,14 @@ +ext["moduleName"] = "org.copperengine.ext" + +dependencies { + implementation(project(":projects:copper-coreengine")) + + implementation("org.eclipse.jgit:org.eclipse.jgit:7.1.0.202411261347-r") + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-tree:9.7") + implementation("commons-io:commons-io:2.16.1") + implementation("com.google.guava:guava:31.0.1-jre") + implementation("org.yaml:snakeyaml:1.33") + + testImplementation("org.slf4j:slf4j-api:2.0.13") +} \ No newline at end of file diff --git a/projects/copper-jmx-interface/build.gradle.kts b/projects/copper-jmx-interface/build.gradle.kts new file mode 100644 index 00000000..719dbfb1 --- /dev/null +++ b/projects/copper-jmx-interface/build.gradle.kts @@ -0,0 +1 @@ +ext["moduleName"] = "org.copperengine.management" \ No newline at end of file diff --git a/projects/copper-regtest/build.gradle.kts b/projects/copper-regtest/build.gradle.kts new file mode 100644 index 00000000..a9e2bc9c --- /dev/null +++ b/projects/copper-regtest/build.gradle.kts @@ -0,0 +1,29 @@ +ext["moduleName"] = "org.copperengine.regtest" + +sourceSets { + create("workflow") { + ext["srcDir"] = "$projectDir/src/workflow/java" + } +} +sourceSets["test"].resources.srcDir(File(sourceSets["workflow"].ext["srcDir"].toString())) + +dependencies { + implementation(project(":projects:copper-coreengine")) + implementation(project(":projects:copper-ext")) + + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-tree:9.7") + implementation("org.yaml:snakeyaml:1.33") + implementation("org.springframework:spring-jdbc:5.3.36") + implementation("org.springframework:spring-context:5.3.36") + implementation("org.springframework:spring-tx:5.3.36") + implementation("com.google.guava:guava:31.0.1-jre") +// testRuntimeOnly(fileTree(mapOf("dir" to "$rootDir/3rdPartyLibs", "include" to "*.jar"))) + + testImplementation("mysql:mysql-connector-java:5.1.25") + testImplementation("org.apache.derby:derby:10.13.1.1") + testImplementation("postgresql:postgresql:9.1-901.jdbc4") + testImplementation("com.h2database:h2:1.4.193") + testImplementation("com.mchange:c3p0:0.10.0") + testImplementation("org.slf4j:slf4j-api:2.0.13") +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 69fd5b53..00000000 --- a/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -include ":projects:copper-coreengine", -":projects:copper-ext", -":projects:copper-jmx-interface", -":projects:copper-regtest" \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..3c2342fa --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,6 @@ +include( + ":projects:copper-coreengine", + ":projects:copper-ext", + ":projects:copper-jmx-interface", + ":projects:copper-regtest" +) \ No newline at end of file From 19bfaee76ddd1baa6cf071d56ff568d66d5762fd Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Mon, 16 Dec 2024 15:04:42 +0100 Subject: [PATCH 22/26] Fix gitlab job name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abf96fe1..9c9c71b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build and upload +name: Build on: push: From 2d1810081a60d8eb712e5d2a0b0c083dd2599c5e Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Tue, 17 Dec 2024 11:57:40 +0100 Subject: [PATCH 23/26] Remove extension of Thread from Processor and use Thread.ofPlatform --- .../core/common/PriorityProcessorPool.java | 2 +- .../copperengine/core/common/Processor.java | 31 ++++++++++++++----- .../tranzient/TransientProcessorFactory.java | 3 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/PriorityProcessorPool.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/PriorityProcessorPool.java index 30d1e170..3c85bdee 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/PriorityProcessorPool.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/PriorityProcessorPool.java @@ -149,7 +149,7 @@ public synchronized void setThreadPriority(int threadPriority) { if (threadPriority != this.threadPriority) { logger.info("ProcessorPool " + id + ": Setting new thread priority to " + threadPriority); this.threadPriority = threadPriority; - for (Thread t : workerThreads) { + for (Processor t : workerThreads) { t.setPriority(threadPriority); } } diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java index 39817a9b..c95e8d4f 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java @@ -28,19 +28,20 @@ * * @author austermann */ -public abstract class Processor extends Thread { +public abstract class Processor implements Runnable { protected static final Logger logger = LoggerFactory.getLogger(Processor.class); protected final Queue> queue; protected volatile boolean shutdown = false; protected final ProcessingEngine engine; protected ProcessingHook processingHook = new MDCProcessingHook(); - private boolean idle = false; + private final Thread thread; + private boolean idle = false; public Processor(String name, Queue> queue, int prio, final ProcessingEngine engine) { - super(name); + this.thread = Thread.ofPlatform().name(name).unstarted(this); this.queue = queue; - this.setPriority(prio); + this.thread.setPriority(prio); this.engine = engine; } @@ -51,9 +52,9 @@ public void setProcessingHook(ProcessingHook processingHook) { public synchronized void shutdown() { if (shutdown) return; - logger.info("Stopping processor '" + getName() + "'..."); + logger.info("Stopping processor '" + this.thread.getName() + "'..."); shutdown = true; - interrupt(); + this.thread.interrupt(); } @Override @@ -107,10 +108,26 @@ protected void preProcess(Workflow wf) { } protected abstract void process(Workflow wf); - + public boolean isIdle() { synchronized (queue) { return idle; } } + + void start() { + thread.start(); + } + + void join() throws InterruptedException { + thread.join(); + } + + void join(final long timeout) throws InterruptedException { + thread.join(timeout); + } + + void setPriority(int threadPriority) { + thread.setPriority(threadPriority); + } } diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessorFactory.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessorFactory.java index 2f037e2b..128fe683 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessorFactory.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessorFactory.java @@ -27,7 +27,8 @@ public class TransientProcessorFactory implements ProcessorFactory { public TransientProcessorFactory() { } - public Processor newProcessor(String id, Queue> queue, int threadPriority, ProcessingEngine engine) { + public Processor + newProcessor(String id, Queue> queue, int threadPriority, ProcessingEngine engine) { return new TransientProcessor(id, queue, threadPriority, engine); } } From 9c3cef269065d8845378c40f21fe35e63f5702c3 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Tue, 17 Dec 2024 12:23:44 +0100 Subject: [PATCH 24/26] Add log for break observation --- .../copperengine/core/wfrepo/FileBasedWorkflowRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/wfrepo/FileBasedWorkflowRepository.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/wfrepo/FileBasedWorkflowRepository.java index 6c2f339f..4636e1e4 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/wfrepo/FileBasedWorkflowRepository.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/wfrepo/FileBasedWorkflowRepository.java @@ -233,6 +233,7 @@ public void run() { try { int checkIntervalMSec = repository.checkIntervalMSec; repository = null; + logger.debug("Break observation for {} milliseconds", checkIntervalMSec); Thread.sleep(checkIntervalMSec); repository = this.repository.get(); if (repository == null) From 83c554ac852364f80ea0c42ddb38620a20998383 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 28 Aug 2025 17:25:26 +0200 Subject: [PATCH 25/26] Support virtual thread usage --- .../copperengine/core/common/Processor.java | 8 +++- .../core/persistent/PersistentProcessor.java | 8 +++- .../PersistentVirtualProcessorFactory.java | 37 +++++++++++++++++++ .../core/tranzient/TransientProcessor.java | 8 +++- .../TransientVirtualProcessorFactory.java | 34 +++++++++++++++++ .../persistent/H2PersistentWorkflowTest.java | 5 +++ .../PersistentEngineTestContext.java | 34 ++++++++++++++++- .../SpringlessBasePersistentWorkflowTest.java | 16 ++++++-- .../tranzient/TransientEngineTestContext.java | 17 ++++++++- .../simple/SimpleTransientEngineTest.java | 12 +++++- .../PersistentUnitTestWorkflow.java | 4 ++ .../TimingOutPersistentUnitTestWorkflow.java | 2 + .../simple/SimpleTransientWorkflow.java | 3 ++ 13 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentVirtualProcessorFactory.java create mode 100644 projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientVirtualProcessorFactory.java diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java index c95e8d4f..8df1c967 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/common/Processor.java @@ -38,8 +38,12 @@ public abstract class Processor implements Runnable { private final Thread thread; private boolean idle = false; - public Processor(String name, Queue> queue, int prio, final ProcessingEngine engine) { - this.thread = Thread.ofPlatform().name(name).unstarted(this); + public Processor(String name, Queue> queue, int prio, final ProcessingEngine engine, final boolean virtual) { + if (virtual) { + this.thread = Thread.ofVirtual().name(name).unstarted(this); + } else { + this.thread = Thread.ofPlatform().name(name).unstarted(this); + } this.queue = queue; this.thread.setPriority(prio); this.engine = engine; diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentProcessor.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentProcessor.java index efd805be..8d3e44d8 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentProcessor.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentProcessor.java @@ -33,8 +33,8 @@ public class PersistentProcessor extends Processor { private final PersistentScottyEngine engine; private final TransactionController transactionController; - public PersistentProcessor(String name, Queue> queue, int prio, ProcessingEngine engine, TransactionController transactionController) { - super(name, queue, prio, engine); + public PersistentProcessor(String name, Queue> queue, int prio, ProcessingEngine engine, TransactionController transactionController, boolean virtual) { + super(name, queue, prio, engine, virtual); if (engine == null) throw new NullPointerException(); if (transactionController == null) @@ -43,6 +43,10 @@ public PersistentProcessor(String name, Queue> queue, int prio, Proc this.transactionController = transactionController; } + public PersistentProcessor(String name, Queue> queue, int prio, ProcessingEngine engine, TransactionController transactionController) { + this(name, queue, prio, engine, transactionController, false); + } + @Override protected void process(final Workflow wf) { final PersistentWorkflow pw = (PersistentWorkflow) wf; diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentVirtualProcessorFactory.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentVirtualProcessorFactory.java new file mode 100644 index 00000000..14c83ef1 --- /dev/null +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/persistent/PersistentVirtualProcessorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2015 SCOOP Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.copperengine.core.persistent; + +import java.util.Queue; + +import org.copperengine.core.ProcessingEngine; +import org.copperengine.core.Workflow; +import org.copperengine.core.common.Processor; +import org.copperengine.core.common.ProcessorFactory; +import org.copperengine.core.persistent.txn.TransactionController; + +public class PersistentVirtualProcessorFactory implements ProcessorFactory { + + private final TransactionController transactionController; + + public PersistentVirtualProcessorFactory(TransactionController transactionController) { + this.transactionController = transactionController; + } + + public Processor newProcessor(String id, Queue> queue, int threadPrioriry, ProcessingEngine engine) { + return new PersistentProcessor(id, queue, threadPrioriry, engine, transactionController, true); + } +} diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessor.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessor.java index ece15e9c..b342c8aa 100644 --- a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessor.java +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientProcessor.java @@ -34,11 +34,15 @@ class TransientProcessor extends Processor { private TransientScottyEngine engine; - public TransientProcessor(String name, Queue> queue, int prio, ProcessingEngine engine) { - super(name, queue, prio, engine); + public TransientProcessor(String name, Queue> queue, int prio, ProcessingEngine engine, boolean virtual) { + super(name, queue, prio, engine, virtual); this.engine = (TransientScottyEngine) engine; } + public TransientProcessor(String name, Queue> queue, int prio, ProcessingEngine engine) { + this(name, queue, prio, engine, false); + } + @Override protected void process(Workflow wf) { logger.trace("before - stack.size()={}", wf.get__stack().size()); diff --git a/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientVirtualProcessorFactory.java b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientVirtualProcessorFactory.java new file mode 100644 index 00000000..241ea553 --- /dev/null +++ b/projects/copper-coreengine/src/main/java/org/copperengine/core/tranzient/TransientVirtualProcessorFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2015 SCOOP Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.copperengine.core.tranzient; + +import java.util.Queue; + +import org.copperengine.core.ProcessingEngine; +import org.copperengine.core.Workflow; +import org.copperengine.core.common.Processor; +import org.copperengine.core.common.ProcessorFactory; + +public class TransientVirtualProcessorFactory implements ProcessorFactory { + + public TransientVirtualProcessorFactory() { + } + + public Processor + newProcessor(String id, Queue> queue, int threadPriority, ProcessingEngine engine) { + return new TransientProcessor(id, queue, threadPriority, engine, true); + } +} diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java index 42953054..569a129d 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/H2PersistentWorkflowTest.java @@ -60,6 +60,11 @@ public void testTimeouts() throws Exception { super.testTimeouts(DS_CONTEXT); } + @Test + public void testTimeoutsWithVirtualThreads() throws Exception { + super.testTimeouts(DS_CONTEXT, true); + } + @Test public void testErrorHandlingInCoreEngine() throws Exception { super.testErrorHandlingInCoreEngine(DS_CONTEXT); diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PersistentEngineTestContext.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PersistentEngineTestContext.java index 247ab9ca..030f8eb8 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PersistentEngineTestContext.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/PersistentEngineTestContext.java @@ -26,12 +26,16 @@ import org.copperengine.core.ProcessingEngine; import org.copperengine.core.audit.BatchingAuditTrail; import org.copperengine.core.batcher.impl.BatcherImpl; +import org.copperengine.core.common.ProcessorPoolManager; import org.copperengine.core.common.WorkflowRepository; import org.copperengine.core.db.utility.RetryingTransaction; import org.copperengine.core.persistent.AbstractSqlDialect; import org.copperengine.core.persistent.DatabaseDialect; import org.copperengine.core.persistent.OracleDialect; +import org.copperengine.core.persistent.PersistentPriorityProcessorPool; +import org.copperengine.core.persistent.PersistentProcessorPool; import org.copperengine.core.persistent.PersistentScottyEngine; +import org.copperengine.core.persistent.PersistentVirtualProcessorFactory; import org.copperengine.core.persistent.lock.PersistentLockManager; import org.copperengine.core.persistent.lock.PersistentLockManagerDialectPostgres; import org.copperengine.core.persistent.lock.PersistentLockManagerDialectSQL; @@ -64,12 +68,18 @@ public class PersistentEngineTestContext extends TestContext { protected final Supplier lockManager; protected final Supplier backchannel; protected final Supplier jmxTestAdapter; + private final boolean virtual; public PersistentEngineTestContext(final DataSourceType dataSourceType, final boolean cleanDB) { - this(dataSourceType, cleanDB, "default", false); + this(dataSourceType, cleanDB, false); } - public PersistentEngineTestContext(final DataSourceType dataSourceType, final boolean cleanDB, final String engineId, final boolean multiEngineMode) { + public PersistentEngineTestContext(final DataSourceType dataSourceType, final boolean cleanDB, final boolean virtual) { + this(dataSourceType, cleanDB, "default", false, virtual); + } + + public PersistentEngineTestContext(final DataSourceType dataSourceType, final boolean cleanDB, final String engineId, final boolean multiEngineMode, final boolean virtual) { + this.virtual = virtual; jmxTestAdapter = Suppliers.memoize(new Supplier() { @Override public JmxTestAdapter get() { @@ -276,6 +286,26 @@ else if (x instanceof AbstractSqlDialect) { return x; } + + @Override + protected ProcessorPoolManager createProcessorPoolManager() { + ProcessorPoolManager processorPoolManager = super.createProcessorPoolManager(); + + if (virtual) { + processorPoolManager + .getProcessorPoolIds() + .stream() + .map(processorPoolManager::getProcessorPool) + .forEach(ppool -> + ((PersistentPriorityProcessorPool) ppool) + .setProcessorFactory( + new PersistentVirtualProcessorFactory(transactionController.get())) + ); + } + + return processorPoolManager; + } + @Override protected TransactionController createTransactionController() { CopperTransactionController txnController = new CopperTransactionController(); diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java index 145df461..6c82bc37 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/persistent/SpringlessBasePersistentWorkflowTest.java @@ -261,7 +261,11 @@ public void testAsynchResponseLargeData(DataSourceType dsType, int dataSize) thr } protected PersistentEngineTestContext createContext(DataSourceType dsType) { - PersistentEngineTestContext ctx = new PersistentEngineTestContext(dsType, true); + return createContext(dsType, false); + } + + protected PersistentEngineTestContext createContext(DataSourceType dsType, boolean virtual) { + PersistentEngineTestContext ctx = new PersistentEngineTestContext(dsType, true, virtual); ctx.startup(); return ctx; } @@ -346,10 +350,14 @@ protected Void execute() throws Exception { } public void testTimeouts(DataSourceType dsType) throws Exception { + testTimeouts(dsType, false); + } + + public void testTimeouts(DataSourceType dsType, boolean virtual) throws Exception { assumeFalse(skipTests()); logger.info("running testTimeouts"); final int NUMB = 10; - final PersistentEngineTestContext context = createContext(dsType); + final PersistentEngineTestContext context = createContext(dsType, virtual); final PersistentScottyEngine engine = context.getEngine(); final BackChannelQueue backChannelQueue = context.getBackChannelQueue(); try { @@ -816,10 +824,10 @@ public void testMultipleEngines(DataSourceType dsType) throws Exception { logger.info("running testMultipleEngines"); final int NUMB = 50; - final PersistentEngineTestContext contextRed = new PersistentEngineTestContext(dsType, true, "red", true); + final PersistentEngineTestContext contextRed = new PersistentEngineTestContext(dsType, true, "red", true, false); contextRed.startup(); - final PersistentEngineTestContext contextBlue = new PersistentEngineTestContext(dsType, false, "blue", true) { + final PersistentEngineTestContext contextBlue = new PersistentEngineTestContext(dsType, false, "blue", true, false) { @Override protected DataHolder createDataHolder() { return contextRed.getDataHolder(); diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/TransientEngineTestContext.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/TransientEngineTestContext.java index a8cf7aad..a35f1eab 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/TransientEngineTestContext.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/TransientEngineTestContext.java @@ -26,6 +26,7 @@ import org.copperengine.core.tranzient.TransientPriorityProcessorPool; import org.copperengine.core.tranzient.TransientProcessorPool; import org.copperengine.core.tranzient.TransientScottyEngine; +import org.copperengine.core.tranzient.TransientVirtualProcessorFactory; import org.copperengine.core.wfrepo.FileBasedWorkflowRepository; import com.google.common.base.Supplier; @@ -40,8 +41,13 @@ public class TransientEngineTestContext extends TestContext { protected final Supplier engine; protected final Supplier repo; protected final Supplier> ppoolManager; + private final boolean virtual; public TransientEngineTestContext() { + this(false); + } + public TransientEngineTestContext(boolean virtual) { + this.virtual = virtual; ppoolManager = Suppliers.memoize(new Supplier>() { @Override public DefaultProcessorPoolManager get() { @@ -94,8 +100,15 @@ protected TransientScottyEngine createTransientScottyEngine() { private DefaultProcessorPoolManager createProcessorPoolManager() { DefaultProcessorPoolManager processorPoolManager = new DefaultProcessorPoolManager(); - processorPoolManager.addProcessorPool(new TransientPriorityProcessorPool(PPOOL_DEFAULT, 4)); - processorPoolManager.addProcessorPool(new TransientPriorityProcessorPool("PS47112", 4)); + final TransientPriorityProcessorPool poolDefault = new TransientPriorityProcessorPool(PPOOL_DEFAULT, 4); + processorPoolManager.addProcessorPool(poolDefault); + final TransientPriorityProcessorPool poolPs47112 = new TransientPriorityProcessorPool("PS47112", 4); + processorPoolManager.addProcessorPool(poolPs47112); + if (virtual) { + final TransientVirtualProcessorFactory processorFactory = new TransientVirtualProcessorFactory(); + poolDefault.setProcessorFactory(processorFactory); + poolPs47112.setProcessorFactory(processorFactory); + } return processorPoolManager; } diff --git a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/simple/SimpleTransientEngineTest.java b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/simple/SimpleTransientEngineTest.java index b15cc131..862dbbc7 100644 --- a/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/simple/SimpleTransientEngineTest.java +++ b/projects/copper-regtest/src/test/java/org/copperengine/regtest/test/tranzient/simple/SimpleTransientEngineTest.java @@ -30,7 +30,16 @@ public class SimpleTransientEngineTest { @Test public void testWorkflow() throws Exception { - try (TransientEngineTestContext ctx = new TransientEngineTestContext()) { + testWorkflow(false); + } + + @Test + public void testWorkflowWithVirtualThreads() throws Exception { + testWorkflow(true); + } + + void testWorkflow(boolean virtual) throws Exception { + try (TransientEngineTestContext ctx = new TransientEngineTestContext(virtual)) { ctx.startup(); assertEquals(EngineState.STARTED, ctx.getEngine().getEngineState()); @@ -38,7 +47,6 @@ public void testWorkflow() throws Exception { WorkflowResult response = ctx.getBackChannelQueue().dequeue(5000, TimeUnit.MILLISECONDS); assertEquals(Integer.valueOf(10), response.getResult()); } - } @Test(expected = DuplicateIdException.class) diff --git a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java index d4388c85..6cc1c5bb 100644 --- a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java +++ b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/PersistentUnitTestWorkflow.java @@ -78,6 +78,8 @@ public void setAuditTrail(AbstractAuditTrail auditTrail) { @Override public void main() throws Interrupt { logger.info("Started!"); + logger.info("Thread=" + Thread.currentThread()); + try { // testWaitAllMultiResponseAndTimeout(); @@ -103,6 +105,8 @@ public void main() throws Interrupt { logger.error("execution failed", e); backChannelQueue.enqueue(new WorkflowResult(null, e)); } + logger.info("Thread=" + Thread.currentThread()); + logger.info("Finished!"); } private String callFoo() throws Interrupt { diff --git a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/TimingOutPersistentUnitTestWorkflow.java b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/TimingOutPersistentUnitTestWorkflow.java index 516c4b1f..13995739 100644 --- a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/TimingOutPersistentUnitTestWorkflow.java +++ b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/persistent/TimingOutPersistentUnitTestWorkflow.java @@ -46,6 +46,7 @@ public void setBackChannelQueue(BackChannelQueue backChannelQueue) { @Override public void main() throws Interrupt { + logger.info("Start Thread=" + Thread.currentThread()); try { String cid = getEngine().createUUID(); wait(WaitMode.ALL, 500, cid); @@ -65,6 +66,7 @@ public void main() throws Interrupt { logger.error("execution failed", e); backChannelQueue.enqueue(new WorkflowResult(null, e)); } + logger.info("End Thread=" + Thread.currentThread()); } } diff --git a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/tranzient/simple/SimpleTransientWorkflow.java b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/tranzient/simple/SimpleTransientWorkflow.java index 53ba2166..7b88b8a5 100644 --- a/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/tranzient/simple/SimpleTransientWorkflow.java +++ b/projects/copper-regtest/src/workflow/java/org/copperengine/core/test/tranzient/simple/SimpleTransientWorkflow.java @@ -63,6 +63,8 @@ public class Innerclass { */ @Override public void main() throws Interrupt { + System.out.println("Start Thread=" + Thread.currentThread()); + new Innerclass(); try { @@ -138,6 +140,7 @@ public void main() throws Interrupt { } finally { System.out.println("finally"); } + System.out.println("End Thread=" + Thread.currentThread()); } private void wait4all(final String cid1, final String cid2) throws Interrupt { From bc76ed4c1d615000d932861947568612a8cfc019 Mon Sep 17 00:00:00 2001 From: Keymaster65 Date: Thu, 28 Aug 2025 18:08:45 +0200 Subject: [PATCH 26/26] Update WHATSNEW.txt --- WHATSNEW.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WHATSNEW.txt b/WHATSNEW.txt index e50f805d..f19a092e 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -1,5 +1,8 @@ COPPER 21.0.0 ============ +- New feature: Optional use of virtual threads in Processor +- Breaking: Remove extension of Thread from Processor +- Breaking: Update to Java 21 - Breaking: Update to Java 21 - Breaking: Exclude from publishing: copper-spring, copper-performance-test and copper-cassandra - Breaking: Update jgit 6.9.0.202403050737-r to 7.1.0.202411261347-r