You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/04/05 18:44:06 UTC
[isis] branch 2033-IoC_spring updated: ISIS-2033: exercise basic
building blocks for transactional invocation
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch 2033-IoC_spring
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/2033-IoC_spring by this push:
new ea8e700 ISIS-2033: exercise basic building blocks for transactional invocation
ea8e700 is described below
commit ea8e700fecbb7567f92dcfc91735d6c188bd9d5a
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Apr 5 20:43:57 2019 +0200
ISIS-2033: exercise basic building blocks for transactional invocation
Task-Url: https://issues.apache.org/jira/browse/ISIS-2033
---
.../metamodel/specloader/SpecificationLoader.java | 1 -
.../specloader/SpecificationLoaderDefault.java | 1 -
.../background/ForkingInvocationHandler.java | 8 +-
example/application/springapp/pom.xml | 42 +++----
.../IncubatingCommandInvocationHandler.java | 9 +-
.../src/main/java/springapp/dom/email/Email.java | 10 +-
.../java/springapp/dom/email/EmailRepository.java | 1 +
.../dom/email/EmailVerificationService.java | 63 +++++++++++
.../springapp/tests/command/CommandDemoBean.java | 34 ++++--
.../java/springapp/tests/command/CommandTest.java | 122 ++++++++++++++++++++-
10 files changed, 243 insertions(+), 48 deletions(-)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
index 9633f90..8f146a6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
@@ -17,7 +17,6 @@
package org.apache.isis.core.metamodel.specloader;
import java.util.List;
-import java.util.stream.Stream;
import javax.annotation.Nullable;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java
index 8a40193..118a730 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java
@@ -2,7 +2,6 @@ package org.apache.isis.core.metamodel.specloader;
import static org.apache.isis.commons.internal.base._With.requires;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
index 8ef5388..a785abb 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
@@ -42,15 +42,15 @@ class ForkingInvocationHandler<T> implements InvocationHandler {
private final T target;
private final Object mixedInIfAny;
- private final ExecutorService backgroundExecutorService;
+ private final ExecutorService executorService;
ForkingInvocationHandler(
T target,
Object mixedInIfAny,
- ExecutorService backgroundExecutorService ) {
+ ExecutorService executorService ) {
this.target = requires(target, "target");
this.mixedInIfAny = mixedInIfAny;
- this.backgroundExecutorService = requires(backgroundExecutorService, "backgroundExecutorService");
+ this.executorService = requires(executorService, "executorService");
}
@Override
@@ -83,7 +83,7 @@ class ForkingInvocationHandler<T> implements InvocationHandler {
.map(IsisTransaction::countDownLatch)
.orElse(new CountDownLatch(0));
- backgroundExecutorService.submit(()->{
+ executorService.submit(()->{
try {
countDownLatch.await(); // wait for current transaction of the calling thread to complete
diff --git a/example/application/springapp/pom.xml b/example/application/springapp/pom.xml
index 6b91637..41555c1 100644
--- a/example/application/springapp/pom.xml
+++ b/example/application/springapp/pom.xml
@@ -45,27 +45,27 @@
<maven-war-plugin.warName>${project.artifactId}</maven-war-plugin.warName>
</properties>
- <build>
- <resources>
- <resource>
- <filtering>true</filtering>
- <directory>src/main/resources</directory>
- <includes>
- <include>**</include>
- </includes>
- </resource>
- <resource>
- <filtering>false</filtering>
- <directory>src/main/java</directory>
- <includes>
- <include>**</include>
- </includes>
- <excludes>
- <exclude>**/*.java</exclude>
- </excludes>
- </resource>
- </resources>
- </build>
+<!-- <build> -->
+<!-- <resources> -->
+<!-- <resource> -->
+<!-- <filtering>true</filtering> -->
+<!-- <directory>src/main/resources</directory> -->
+<!-- <includes> -->
+<!-- <include>**</include> -->
+<!-- </includes> -->
+<!-- </resource> -->
+<!-- <resource> -->
+<!-- <filtering>false</filtering> -->
+<!-- <directory>src/main/java</directory> -->
+<!-- <includes> -->
+<!-- <include>**</include> -->
+<!-- </includes> -->
+<!-- <excludes> -->
+<!-- <exclude>**/*.java</exclude> -->
+<!-- </excludes> -->
+<!-- </resource> -->
+<!-- </resources> -->
+<!-- </build> -->
<dependencyManagement>
<dependencies>
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java
index bd9c084..c01219f 100644
--- a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java
+++ b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java
@@ -2,11 +2,12 @@ package isis.incubator.command;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.function.Supplier;
import org.apache.isis.applib.services.background.CommandSchedulerService;
import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapterProvider;
+import org.apache.isis.commons.internal.debug._Probe;
import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -22,10 +23,14 @@ class IncubatingCommandInvocationHandler<T> implements InvocationHandler {
@NonNull private final SpecificationLoader specificationLoader;
@NonNull private final CommandDtoServiceInternal commandDtoServiceInternal;
@NonNull private final Supplier<Command> toplevelCommandSupplier;
- @NonNull private final Supplier<ObjectAdapterProvider> adapterProviderSupplier;
+// @NonNull private final Supplier<ObjectAdapterProvider> adapterProviderSupplier;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+
+ _Probe.errOut("invoke not implemented: proxy='%s', method='%s', args='%s'",
+ proxy.getClass(), method.getName(), ""+Arrays.asList(args));
+
// TODO Auto-generated method stub
return null;
}
diff --git a/example/application/springapp/src/main/java/springapp/dom/email/Email.java b/example/application/springapp/src/main/java/springapp/dom/email/Email.java
index cb7f4d9..a585fc6 100644
--- a/example/application/springapp/src/main/java/springapp/dom/email/Email.java
+++ b/example/application/springapp/src/main/java/springapp/dom/email/Email.java
@@ -20,7 +20,8 @@ import springapp.dom.customer.Customer;
@DomainObject(nature=Nature.EXTERNAL_ENTITY)
@Entity
-@NoArgsConstructor(access = AccessLevel.PROTECTED) @RequiredArgsConstructor @ToString
+@NoArgsConstructor(access = AccessLevel.PROTECTED) @RequiredArgsConstructor
+@ToString(exclude = {"customer"})
public class Email {
@Id
@@ -37,13 +38,6 @@ public class Email {
@Getter @Setter
private boolean verified;
-
- // -- BUSINESS LOGIC
-
- public void startVerificationProcess() {
-
- System.out.println("startVerificationProcess for " + this);
- }
}
diff --git a/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java b/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java
index 4aee42e..57ea186 100644
--- a/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java
+++ b/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java
@@ -7,5 +7,6 @@ import org.springframework.data.repository.CrudRepository;
public interface EmailRepository extends CrudRepository<Email, Long> {
List<Email> findByVerified(boolean verified);
+ long countByVerified(boolean verified);
}
diff --git a/example/application/springapp/src/main/java/springapp/dom/email/EmailVerificationService.java b/example/application/springapp/src/main/java/springapp/dom/email/EmailVerificationService.java
new file mode 100644
index 0000000..83f2238
--- /dev/null
+++ b/example/application/springapp/src/main/java/springapp/dom/email/EmailVerificationService.java
@@ -0,0 +1,63 @@
+package springapp.dom.email;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.AsyncResult;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
+
+@Service @Slf4j
+public class EmailVerificationService {
+
+ // -- BUSINESS LOGIC
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void startVerificationProcess(Email email) {
+
+ log.info("start process for {}", toInfo(email));
+
+ email.setVerified(true);
+
+ log.info("end process for {}", toInfo(email));
+ }
+
+
+ @Async @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public CompletableFuture<Void> startVerificationProcessAsync(Email email) {
+
+ log.info("start process for {}", toInfo(email));
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ email.setVerified(true);
+
+ log.info("end process for {}", toInfo(email));
+
+ return new AsyncResult<Void>(null).completable();
+ }
+
+ // -- HELPER
+
+ private String toInfo(Email email) {
+
+ val sb = new StringBuilder();
+
+ //TODO what transaction are we currently in, if any?
+
+ sb
+ .append(", email.verified=")
+ .append(email.isVerified());
+
+ return sb.toString();
+ }
+
+}
diff --git a/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java b/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java
index a4521d2..68b2280 100644
--- a/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java
+++ b/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java
@@ -2,28 +2,42 @@ package springapp.tests.command;
import javax.inject.Inject;
-import org.apache.isis.applib.annotation.Action;
-import org.apache.isis.applib.services.background.BackgroundExecutionService;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import lombok.val;
+import springapp.dom.customer.Customer;
import springapp.dom.customer.CustomerRepository;
import springapp.dom.email.Email;
import springapp.dom.email.EmailRepository;
+import springapp.dom.email.EmailVerificationService;
-@Service
+@Service @Transactional
public class CommandDemoBean {
@Inject EmailRepository emailRepository;
@Inject CustomerRepository customerRepository;
- @Inject BackgroundExecutionService backgroundService;
+ @Inject EmailVerificationService emailVerificationService;
+ //@Inject PlatformTransactionManager platformTransactionManager;
- @Action
- public void verifyCustomerEmails() {
+
+ public void setUp() {
+ val customer = customerRepository.save(new Customer("Jack", "Bauer"));
+ val email = emailRepository.save(new Email(customer, "j.bauer@icu.fiction"));
+ }
+
+ public void setUpVerified() {
+ val customer = customerRepository.save(new Customer("Jack", "Bauer"));
- for(Email email: emailRepository.findByVerified(false)) {
- backgroundService.execute(email).startVerificationProcess();
- }
-
+ val email = new Email(customer, "j.bauer@icu.fiction");
+ email.setVerified(true);
+ emailRepository.save(email);
+ }
+
+
+ public void cleanup() {
+ emailRepository.deleteAll();
+ customerRepository.deleteAll();
}
}
diff --git a/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java b/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java
index 99c3c1c..f293b12 100644
--- a/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java
+++ b/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java
@@ -1,6 +1,11 @@
package springapp.tests.command;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import javax.inject.Inject;
+
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -9,22 +14,137 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.test.context.junit.jupiter.SpringExtension;
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
import springapp.boot.test.SpringBootTestApplication;
+import springapp.dom.customer.CustomerRepository;
+import springapp.dom.email.Email;
+import springapp.dom.email.EmailRepository;
+import springapp.dom.email.EmailVerificationService;
+import springapp.tests.async.Futures;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {SpringBootTestApplication.class, CommandDemoBean.class})
@AutoConfigureTestDatabase
@EnableAsync
@TestMethodOrder(OrderAnnotation.class)
+@Slf4j
class CommandTest {
- //@Inject AsyncExecutionService asyncExecutionService;
+ @Inject CommandDemoBean commandDemoBean;
+ @Inject EmailRepository emailRepository;
+ @Inject CustomerRepository customerRepository;
+ @Inject EmailVerificationService emailVerificationService;
+
+ static {
+ System.setProperty("logging.level.org.springframework.transaction", "TRACE");
+ System.setProperty("logging.level.org.springframework.orm", "TRACE");
+ }
+
+ @AfterEach
+ void cleanup() {
+ commandDemoBean.cleanup();
+ }
+
+ @Test
+ void shouldProperlyCountVerifiedEmails_not_verified() {
+
+ commandDemoBean.setUp();
+
+ val verifiedCount = emailRepository.countByVerified(true);
+ val notVerifiedCount = emailRepository.countByVerified(false);
+
+ System.out.println("verified: " + verifiedCount);
+ System.out.println("not verified: " + notVerifiedCount);
+
+ assertEquals(0, verifiedCount);
+ assertEquals(1, notVerifiedCount);
+
+ }
+
+ @Test
+ void shouldProperlyCountVerifiedEmails_verified() {
+ commandDemoBean.setUpVerified();
+
+ val verifiedCount = emailRepository.countByVerified(true);
+ val notVerifiedCount = emailRepository.countByVerified(false);
+
+ System.out.println("verified: " + verifiedCount);
+ System.out.println("not verified: " + notVerifiedCount);
+
+ assertEquals(1, verifiedCount);
+ assertEquals(0, notVerifiedCount);
+
+ }
+
@Test
void shouldAllowTaskCancellation() {
+ commandDemoBean.setUp();
+
+ val futures = new Futures<Void>();
+
+ for(Email email: emailRepository.findByVerified(false)) {
+
+ // we want this to run within its own transaction and in the background (async)
+ // 'emailVerificationService' is managed by Spring, so the invocation honors any
+ // @Transactional and @Async annotation present on the method that gets invoked.
+ val future = emailVerificationService.startVerificationProcessAsync(email);
+
+ futures.add(future);
+
+ //backgroundService.execute(email).startVerificationProcess();
+ }
+
+ log.info("join on the futures and wait for completion");
+
+ // join on the futures and wait for completion
+ futures.combine().join();
+
+ log.info("all futures completed");
+
+ {
+ val verifiedCount = emailRepository.countByVerified(true);
+ val notVerifiedCount = emailRepository.countByVerified(false);
+ System.out.println("verified: " + verifiedCount);
+ System.out.println("not verified: " + notVerifiedCount);
+
+ assertEquals(1, verifiedCount);
+ assertEquals(0, notVerifiedCount);
+ }
+
+
+
+ }
+
+ @Test
+ void emailVerificationNonAsync() {
+ commandDemoBean.setUp();
+
+ for(Email email: emailRepository.findByVerified(false)) {
+
+ // we want this to run within its own transaction and in the background (async)
+ // 'emailVerificationService' is managed by Spring, so the invocation honors any
+ // @Transactional and @Async annotation present on the method that gets invoked.
+ emailVerificationService.startVerificationProcess(email);
+
+ //backgroundService.execute(email).startVerificationProcess();
+ }
+
+ {
+ val verifiedCount = emailRepository.countByVerified(true);
+ val notVerifiedCount = emailRepository.countByVerified(false);
+ System.out.println("verified: " + verifiedCount);
+ System.out.println("not verified: " + notVerifiedCount);
+
+ assertEquals(1, verifiedCount);
+ assertEquals(0, notVerifiedCount);
+ }
+
}
+
}