Skip to content

Commit bbece9c

Browse files
authored
Merge pull request #5690 from vidakovic/feature/FINERACT-2469
FINERACT-2469: New command processing - add pre/post/error processor concept
2 parents 98968e5 + 7d7371a commit bbece9c

File tree

145 files changed

+3119
-979
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+3119
-979
lines changed

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ buildscript {
3030
'fineract-cob',
3131
'fineract-validation',
3232
'fineract-command',
33+
'fineract-command-test',
34+
'fineract-command-jdbc',
35+
'fineract-command-audit',
36+
'fineract-command-async',
37+
'fineract-command-disruptor',
3338
'fineract-accounting',
3439
'fineract-provider',
3540
'fineract-branch',
@@ -66,6 +71,8 @@ buildscript {
6671
'fineract-cob',
6772
'fineract-validation',
6873
'fineract-command',
74+
'fineract-command-jdbc',
75+
'fineract-command-audit',
6976
'fineract-accounting',
7077
'fineract-provider',
7178
'fineract-investor',

buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ dependencyManagement {
280280
dependency 'org.springframework.restdocs:spring-restdocs-webtestclient:3.0.3'
281281
dependency 'org.springframework.restdocs:spring-restdocs-restassured:3.0.3'
282282

283-
dependency 'com.lmax:disruptor:3.4.4'
283+
dependency 'com.lmax:disruptor:4.0.0'
284284

285285
dependency ('com.github.romankh3:image-comparison:4.4.0') {
286286
exclude 'org.junit.jupiter:junit-jupiter-api'

config/checkstyle/suppressions.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@
3333
<suppress files="[/\\]fineract-command[/\\]src[/\\]main[/\\]java[/\\]" checks="MethodTypeParameterName"/>
3434
<suppress files="[/\\]fineract-command[/\\]src[/\\]main[/\\]java[/\\]" checks="ClassTypeParameterName"/>
3535
<suppress files="[/\\]fineract-command[/\\]src[/\\]main[/\\]java[/\\]" checks="HideUtilityClassConstructor"/>
36+
<suppress files="[/\\]fineract-command-async[/\\]src[/\\]main[/\\]java[/\\]" checks="MethodTypeParameterName"/>
37+
<suppress files="[/\\]fineract-command-async[/\\]src[/\\]main[/\\]java[/\\]" checks="ClassTypeParameterName"/>
38+
<suppress files="[/\\]fineract-command-async[/\\]src[/\\]main[/\\]java[/\\]" checks="HideUtilityClassConstructor"/>
39+
<suppress files="[/\\]fineract-command-disruptor[/\\]src[/\\]main[/\\]java[/\\]" checks="MethodTypeParameterName"/>
40+
<suppress files="[/\\]fineract-command-disruptor[/\\]src[/\\]main[/\\]java[/\\]" checks="ClassTypeParameterName"/>
41+
<suppress files="[/\\]fineract-command-disruptor[/\\]src[/\\]main[/\\]java[/\\]" checks="HideUtilityClassConstructor"/>
42+
<suppress files="[/\\]fineract-command-disruptor[/\\]build[/\\]generated[/\\]" checks=".*"/>
3643
</suppressions>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
description = 'Fineract Command - Async'
20+
21+
apply plugin: 'java'
22+
apply plugin: 'eclipse'
23+
apply plugin: 'me.champeau.jmh'
24+
25+
configurations {
26+
asciidoctorExtensions
27+
providedRuntime // needed for Spring Boot executable WAR
28+
providedCompile
29+
compile() {
30+
exclude module: 'hibernate-entitymanager'
31+
exclude module: 'hibernate-validator'
32+
exclude module: 'activation'
33+
exclude module: 'bcmail-jdk14'
34+
exclude module: 'bcprov-jdk14'
35+
exclude module: 'bctsp-jdk14'
36+
exclude module: 'c3p0'
37+
exclude module: 'stax-api'
38+
exclude module: 'jaxb-api'
39+
exclude module: 'jaxb-impl'
40+
exclude module: 'jboss-logging'
41+
exclude module: 'itext-rtf'
42+
exclude module: 'classworlds'
43+
}
44+
runtime
45+
}
46+
47+
apply from: 'dependencies.gradle'
48+
49+
// Configuration for the modernizer plugin
50+
// https://github.com/andygoossens/gradle-modernizer-plugin
51+
modernizer {
52+
ignoreClassNamePatterns = [
53+
'.*AbstractPersistableCustom',
54+
'.*EntityTables',
55+
'.*domain.*'
56+
]
57+
}
58+
59+
// If we are running Gradle within Eclipse to enhance classes with OpenJPA,
60+
// set the classes directory to point to Eclipse's default build directory
61+
if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') {
62+
sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-command-async/bin/main")
63+
}
64+
65+
if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
66+
sourceSets {
67+
test {
68+
java {
69+
exclude '**/core/boot/tests/**'
70+
}
71+
}
72+
}
73+
}
74+
75+
jmh {
76+
// include = ['.*'] // Include all benchmarks (regex-based filter)
77+
warmupIterations = 2 // Number of warm-up iterations
78+
iterations = 3 // Number of measurement iterations
79+
fork = 1 // Number of forks
80+
timeOnIteration = '2s' // Time per iteration
81+
benchmarkMode = ['thrpt'] // Default benchmark mode
82+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
dependencies {
21+
implementation(
22+
project(path: ':fineract-command'),
23+
)
24+
25+
implementation(
26+
'org.springframework.boot:spring-boot-starter',
27+
'io.github.resilience4j:resilience4j-spring-boot3',
28+
29+
'org.apache.commons:commons-lang3',
30+
31+
'com.github.spotbugs:spotbugs-annotations',
32+
'org.mapstruct:mapstruct',
33+
)
34+
compileOnly 'org.projectlombok:lombok'
35+
annotationProcessor 'org.projectlombok:lombok'
36+
annotationProcessor 'org.mapstruct:mapstruct-processor'
37+
jmh 'org.openjdk.jmh:jmh-generator-annprocess'
38+
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess'
39+
40+
testImplementation(project(path: ':fineract-command-test'))
41+
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
42+
exclude group: 'com.jayway.jsonpath', module: 'json-path'
43+
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
44+
exclude group: 'jakarta.activation'
45+
exclude group: 'javax.activation'
46+
exclude group: 'org.skyscreamer'
47+
}
48+
testImplementation (
49+
'org.mockito:mockito-inline',
50+
'org.openjdk.jmh:jmh-core',
51+
'org.testcontainers:junit-jupiter',
52+
)
53+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.command.async;
20+
21+
import java.io.Serial;
22+
import java.io.Serializable;
23+
import java.time.Duration;
24+
import lombok.AllArgsConstructor;
25+
import lombok.Builder;
26+
import lombok.Data;
27+
import lombok.NoArgsConstructor;
28+
import lombok.experimental.FieldNameConstants;
29+
import org.springframework.boot.context.properties.ConfigurationProperties;
30+
31+
@Builder
32+
@Data
33+
@NoArgsConstructor
34+
@AllArgsConstructor
35+
@FieldNameConstants
36+
@ConfigurationProperties(prefix = "fineract.command.async")
37+
public final class AsyncCommandProperties implements Serializable {
38+
39+
@Serial
40+
private static final long serialVersionUID = 1L;
41+
42+
@Builder.Default
43+
private Boolean enabled = false;
44+
45+
@Builder.Default
46+
private Duration timeout = Duration.ofSeconds(3L);
47+
}

fineract-command/src/main/java/org/apache/fineract/command/implementation/AsynchronousCommandExecutor.java renamed to fineract-command-async/src/main/java/org/apache/fineract/command/async/implementation/AsyncCommandDispatcher.java

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,57 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
package org.apache.fineract.command.implementation;
19+
package org.apache.fineract.command.async.implementation;
2020

21+
import static java.util.Objects.requireNonNull;
2122
import static java.util.concurrent.TimeUnit.SECONDS;
2223

2324
import java.util.concurrent.CompletableFuture;
2425
import java.util.concurrent.ExecutionException;
2526
import java.util.concurrent.TimeoutException;
2627
import java.util.function.Supplier;
28+
import lombok.RequiredArgsConstructor;
2729
import lombok.extern.slf4j.Slf4j;
2830
import org.apache.fineract.command.core.Command;
29-
import org.apache.fineract.command.core.CommandAuditor;
30-
import org.apache.fineract.command.core.CommandHandler;
31-
import org.apache.fineract.command.core.CommandRouter;
31+
import org.apache.fineract.command.core.CommandDispatcher;
32+
import org.apache.fineract.command.core.CommandHandlerManager;
33+
import org.apache.fineract.command.core.CommandHookManager;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35+
import org.springframework.stereotype.Component;
3236

37+
// TODO: WIP - not ready yet for prime time
3338
@Slf4j
34-
// TODO: not ready yet for prime time
35-
// @Component
36-
// @ConditionalOnProperty(value = "fineract.command.executor", havingValue = "async")
37-
public class AsynchronousCommandExecutor extends BaseCommandExecutor {
39+
@RequiredArgsConstructor
40+
@Component
41+
@ConditionalOnProperty(value = "fineract.command.async.enabled", havingValue = "true")
42+
public class AsyncCommandDispatcher implements CommandDispatcher {
3843

39-
public AsynchronousCommandExecutor(CommandRouter router, CommandAuditor auditor) {
40-
super(router, auditor);
41-
}
44+
private final CommandHandlerManager handlerManager;
45+
private final CommandHookManager hookManager;
4246

4347
@Override
44-
public <REQ, RES> Supplier<RES> execute(final Command<REQ> command) {
48+
public <REQ, RES> Supplier<RES> dispatch(final Command<REQ> command) {
49+
requireNonNull(command, "Command must not be null");
50+
4551
CompletableFuture<RES> future = CompletableFuture.supplyAsync(() -> {
46-
auditor.processing(command);
52+
hookManager.before(command);
4753

48-
CommandHandler<REQ, RES> handler = router.route(command);
54+
RES response = handlerManager.handle(command);
4955

50-
return handler.handle(command);
51-
}).whenComplete((response, t) -> {
52-
if (t == null) {
53-
auditor.processed(command, response);
54-
} else {
55-
command.setError(t.getMessage());
56+
hookManager.after(command, response);
5657

57-
auditor.error(command);
58+
return response;
59+
}).whenComplete((response, t) -> {
60+
if (t != null) {
61+
hookManager.error(command, t);
5862
}
5963
});
6064

6165
return () -> {
6266
try {
6367
// TODO: make this configurable
6468
return future.get(3, SECONDS);
65-
} catch (InterruptedException e) {
66-
throw new RuntimeException(e);
67-
} catch (ExecutionException e) {
68-
throw new RuntimeException(e);
69-
} catch (TimeoutException e) {
69+
} catch (InterruptedException | ExecutionException | TimeoutException e) {
7070
throw new RuntimeException(e);
7171
}
7272
};

fineract-command/src/main/java/org/apache/fineract/command/implementation/BaseCommandExecutor.java renamed to fineract-command-async/src/main/java/org/apache/fineract/command/async/starter/AsyncCommandAutoConfiguration.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,16 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
package org.apache.fineract.command.implementation;
19+
package org.apache.fineract.command.async.starter;
2020

21-
import org.apache.fineract.command.core.CommandAuditor;
22-
import org.apache.fineract.command.core.CommandExecutor;
23-
import org.apache.fineract.command.core.CommandRouter;
21+
import org.apache.fineract.command.async.AsyncCommandProperties;
22+
import org.springframework.boot.autoconfigure.AutoConfiguration;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
24+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
25+
import org.springframework.context.annotation.ComponentScan;
2426

25-
public abstract class BaseCommandExecutor implements CommandExecutor {
26-
27-
protected final CommandRouter router;
28-
protected final CommandAuditor auditor;
29-
30-
protected BaseCommandExecutor(CommandRouter router, CommandAuditor auditor) {
31-
this.router = router;
32-
this.auditor = auditor;
33-
}
34-
}
27+
@AutoConfiguration
28+
@EnableConfigurationProperties(AsyncCommandProperties.class)
29+
@ComponentScan("org.apache.fineract.command.async.implementation")
30+
@ConditionalOnProperty(value = "fineract.command.async.enabled", havingValue = "true")
31+
public class AsyncCommandAutoConfiguration {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.apache.fineract.command.async.starter.AsyncCommandAutoConfiguration
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.command.async;
20+
21+
import static org.mockito.Mockito.mock;
22+
23+
import lombok.extern.slf4j.Slf4j;
24+
import org.apache.fineract.command.core.CommandProperties;
25+
import org.apache.fineract.command.core.CommandStore;
26+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
27+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
28+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.ComponentScan;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.context.annotation.PropertySource;
33+
34+
@Slf4j
35+
@Configuration
36+
@EnableConfigurationProperties({ CommandProperties.class, AsyncCommandProperties.class })
37+
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
38+
@PropertySource("classpath:application-test.properties")
39+
@ComponentScan("org.apache.fineract.command.async.implementation")
40+
@ComponentScan("org.apache.fineract.command.test.sample")
41+
class TestConfiguration {
42+
43+
@Bean
44+
CommandStore commandStore() {
45+
return mock(CommandStore.class);
46+
}
47+
}

0 commit comments

Comments
 (0)