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);
+		}
+		
 	}
+	
 
 
 }