This is an automated email from the ASF dual-hosted git repository.

ningjiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-saga.git

commit 0eb405e9b08000317a7412aeabe8ae41e0518a8a
Author: seanyinx <sean....@huawei.com>
AuthorDate: Sat Dec 23 15:24:54 2017 +0800

    SCB-96 invoked compensation method annotated with Compensable
    
    Signed-off-by: seanyinx <sean....@huawei.com>
---
 .../saga/omega/context/OmegaContext.java           | 57 +++++++++++++++++++++-
 omega/omega-spring-tx/pom.xml                      |  9 ++--
 .../spring/TransactionInterceptionTest.java        | 42 ++++++++++++++--
 .../spring/TransactionalUserService.java           | 14 ++++--
 .../saga/omega/transaction/spring/User.java        | 26 +++++++++-
 .../omega/transaction/MessageDeserializer.java}    | 22 ++-------
 .../saga/omega/transaction/MessageHandler.java}    | 22 ++-------
 .../saga/omega/transaction/TransactionAspect.java  | 12 +++--
 .../transaction/annotations/Compensable.java}      | 27 ++++------
 9 files changed, 158 insertions(+), 73 deletions(-)

diff --git 
a/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java
 
b/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java
index 9884349..8b41d9a 100644
--- 
a/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java
+++ 
b/omega/omega-context/src/main/java/io/servicecomb/saga/omega/context/OmegaContext.java
@@ -17,11 +17,16 @@
 
 package io.servicecomb.saga.omega.context;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 public class OmegaContext {
   private final ThreadLocal<String> globalTxId = new ThreadLocal<>();
   private final ThreadLocal<String> localTxId = new ThreadLocal<>();
   private final ThreadLocal<String> parentTxId = new ThreadLocal<>();
-
+  private final Map<String, CompensationContext> compensationContexts = new 
ConcurrentHashMap<>();
 
   public void setGlobalTxId(String txId) {
     globalTxId.set(txId);
@@ -47,6 +52,44 @@ public class OmegaContext {
     this.parentTxId.set(parentTxId);
   }
 
+  // TODO: 2017/12/23 remove this context entry by the end of its 
corresponding global tx
+  public void addContext(String id, Object target, String compensationMethod, 
Object... args) {
+    compensationContexts.put(id, new CompensationContext(target, 
compensationMethod, args));
+  }
+
+  public void compensate(String globalTxId) {
+    CompensationContext compensationContext = 
compensationContexts.get(globalTxId);
+
+    try {
+      invokeMethod(compensationContext);
+    } catch (NoSuchMethodException | IllegalAccessException | 
InvocationTargetException e) {
+      throw new IllegalStateException(
+          "Pre-checking for compensate method " + 
compensationContext.compensationMethod + " was somehow skipped",
+          e);
+    }
+  }
+
+  private void invokeMethod(CompensationContext compensationContext)
+      throws NoSuchMethodException, IllegalAccessException, 
InvocationTargetException {
+
+    Method method = compensationContext.target
+        .getClass()
+        .getDeclaredMethod(compensationContext.compensationMethod, 
argClasses(compensationContext));
+    method.setAccessible(true);
+
+    method.invoke(compensationContext.target, compensationContext.args);
+  }
+
+  private Class<?>[] argClasses(CompensationContext compensationContext) {
+    Class<?>[] classes = new Class<?>[compensationContext.args.length];
+
+    for (int i = 0; i < compensationContext.args.length; i++) {
+      classes[i] = compensationContext.args[i].getClass();
+    }
+
+    return classes;
+  }
+
   @Override
   public String toString() {
     return "OmegaContext{" +
@@ -55,4 +98,16 @@ public class OmegaContext {
         ", parentTxId=" + parentTxId.get() +
         '}';
   }
+
+  private static final class CompensationContext {
+    private final Object target;
+    private final String compensationMethod;
+    private final Object[] args;
+
+    private CompensationContext(Object target, String compensationMethod, 
Object... args) {
+      this.target = target;
+      this.compensationMethod = compensationMethod;
+      this.args = args;
+    }
+  }
 }
diff --git a/omega/omega-spring-tx/pom.xml b/omega/omega-spring-tx/pom.xml
index b88f231..6019650 100644
--- a/omega/omega-spring-tx/pom.xml
+++ b/omega/omega-spring-tx/pom.xml
@@ -33,10 +33,6 @@
       <artifactId>spring-boot-starter</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-data-jpa</artifactId>
-    </dependency>
-    <dependency>
       <groupId>io.servicecomb.saga</groupId>
       <artifactId>omega-context</artifactId>
     </dependency>
@@ -71,6 +67,11 @@
       <artifactId>h2</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-data-jpa</artifactId>
+      <scope>test</scope>
+    </dependency>
 
   </dependencies>
 
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java
 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java
index 0c4fbab..27ee479 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java
+++ 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionInterceptionTest.java
@@ -19,33 +19,40 @@ package io.servicecomb.saga.omega.transaction.spring;
 
 import static com.seanyinx.github.unit.scaffolding.Randomness.uniquify;
 import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
+import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.test.context.junit4.SpringRunner;
 
 import io.servicecomb.saga.omega.context.OmegaContext;
+import io.servicecomb.saga.omega.transaction.MessageHandler;
 import io.servicecomb.saga.omega.transaction.MessageSender;
 import io.servicecomb.saga.omega.transaction.MessageSerializer;
 import 
io.servicecomb.saga.omega.transaction.spring.TransactionInterceptionTest.MessageConfig;
 
 @RunWith(SpringRunner.class)
 @SpringBootTest(classes = {TransactionTestMain.class, MessageConfig.class})
+@AutoConfigureMockMvc
 public class TransactionInterceptionTest {
   private static final String TX_STARTED_EVENT = "TxStartedEvent";
   private static final String TX_ENDED_EVENT = "TxEndedEvent";
-  private final String globalTxId = UUID.randomUUID().toString();
+  private static final String globalTxId = UUID.randomUUID().toString();
   private final String localTxId = UUID.randomUUID().toString();
   private final String parentTxId = UUID.randomUUID().toString();
   private final String username = uniquify("username");
@@ -60,6 +67,12 @@ public class TransactionInterceptionTest {
   @Autowired
   private OmegaContext omegaContext;
 
+  @Autowired
+  private UserRepository userRepository;
+
+  @Autowired
+  private MessageHandler messageHandler;
+
   @Before
   public void setUp() throws Exception {
     omegaContext.setGlobalTxId(globalTxId);
@@ -67,9 +80,14 @@ public class TransactionInterceptionTest {
     omegaContext.setParentTxId(parentTxId);
   }
 
+  @After
+  public void tearDown() throws Exception {
+    messages.clear();
+  }
+
   @Test
-  public void sendsUserToRemote_BeforeTransaction() throws Exception {
-    userService.add(new User(username, email));
+  public void sendsUserToRemote_AroundTransaction() throws Exception {
+    User user = userService.add(new User(username, email));
 
     assertEquals(
         asList(
@@ -77,6 +95,19 @@ public class TransactionInterceptionTest {
             txEndedEvent(globalTxId, localTxId, parentTxId)),
         toString(messages)
     );
+
+    User actual = userRepository.findOne(user.id());
+    assertThat(actual, is(user));
+  }
+
+  @Test
+  public void compensateOnTransactionException() throws Exception {
+    User user = userService.add(new User(username, email));
+
+    messageHandler.onReceive("to be compensated".getBytes());
+
+    User actual = userRepository.findOne(user.id());
+    assertThat(actual, is(nullValue()));
   }
 
   private List<String> toString(List<byte[]> messages) {
@@ -115,6 +146,11 @@ public class TransactionInterceptionTest {
             event.parentTxId()).getBytes();
       };
     }
+
+    @Bean
+    MessageHandler handler(OmegaContext omegaContext) {
+      return bytes -> omegaContext.compensate(globalTxId);
+    }
   }
 
   private static String txStartedEvent(String globalTxId,
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
index 1d17c6c..74cb59e 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
+++ 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
@@ -17,11 +17,11 @@
 
 package io.servicecomb.saga.omega.transaction.spring;
 
-import javax.transaction.Transactional;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import io.servicecomb.saga.omega.transaction.annotations.Compensable;
+
 @Component
 class TransactionalUserService {
   private final UserRepository userRepository;
@@ -31,8 +31,12 @@ class TransactionalUserService {
     this.userRepository = userRepository;
   }
 
-  @Transactional
-  void add(User user) {
-    userRepository.save(user);
+  @Compensable(compensationMethod = "delete")
+  User add(User user) {
+    return userRepository.save(user);
+  }
+
+  void delete(User user) {
+    userRepository.delete(user.id());
   }
 }
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java
 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java
index a90d1d6..6b2e55f 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java
+++ 
b/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/User.java
@@ -17,6 +17,8 @@
 
 package io.servicecomb.saga.omega.transaction.spring;
 
+import java.util.Objects;
+
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
@@ -30,12 +32,15 @@ public class User {
   private String username;
   private String email;
 
+  User() {
+  }
+
   User(String username, String email) {
     this.username = username;
     this.email = email;
   }
 
-  public long id() {
+  long id() {
     return id;
   }
 
@@ -46,4 +51,23 @@ public class User {
   String email() {
     return email;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    User user = (User) o;
+    return id == user.id &&
+        Objects.equals(username, user.username) &&
+        Objects.equals(email, user.email);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(id, username, email);
+  }
 }
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java
similarity index 61%
copy from 
omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
copy to 
omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java
index 1d17c6c..d4315b3 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
+++ 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageDeserializer.java
@@ -15,24 +15,8 @@
  * limitations under the License.
  */
 
-package io.servicecomb.saga.omega.transaction.spring;
+package io.servicecomb.saga.omega.transaction;
 
-import javax.transaction.Transactional;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-class TransactionalUserService {
-  private final UserRepository userRepository;
-
-  @Autowired
-  TransactionalUserService(UserRepository userRepository) {
-    this.userRepository = userRepository;
-  }
-
-  @Transactional
-  void add(User user) {
-    userRepository.save(user);
-  }
+public interface MessageDeserializer {
+  <T> T deserialize(byte[] message);
 }
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java
similarity index 61%
copy from 
omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
copy to 
omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java
index 1d17c6c..e954381 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
+++ 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/MessageHandler.java
@@ -15,24 +15,8 @@
  * limitations under the License.
  */
 
-package io.servicecomb.saga.omega.transaction.spring;
+package io.servicecomb.saga.omega.transaction;
 
-import javax.transaction.Transactional;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-class TransactionalUserService {
-  private final UserRepository userRepository;
-
-  @Autowired
-  TransactionalUserService(UserRepository userRepository) {
-    this.userRepository = userRepository;
-  }
-
-  @Transactional
-  void add(User user) {
-    userRepository.save(user);
-  }
+public interface MessageHandler {
+  void onReceive(byte[] message);
 }
diff --git 
a/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java
 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java
index e17861d..6d13a04 100644
--- 
a/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java
+++ 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/TransactionAspect.java
@@ -28,6 +28,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import io.servicecomb.saga.omega.context.OmegaContext;
+import io.servicecomb.saga.omega.transaction.annotations.Compensable;
 
 @Aspect
 public class TransactionAspect {
@@ -42,10 +43,15 @@ public class TransactionAspect {
     this.postTransactionInterceptor = new PostTransactionInterceptor(sender, 
serializer);
   }
 
-  @Around("execution(@javax.transaction.Transactional * *(..))")
-  Object advise(ProceedingJoinPoint joinPoint) throws Throwable {
+  
@Around("execution(@io.servicecomb.saga.omega.transaction.annotations.Compensable
 * *(..)) && @annotation(compensable)")
+  Object advise(ProceedingJoinPoint joinPoint, Compensable compensable) throws 
Throwable {
     Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
-    LOG.debug("Intercepting transactional method {} with context {}", 
method.toString(), context);
+    LOG.debug("Intercepting compensable method {} with context {}", 
method.toString(), context);
+
+    context.addContext(context.globalTxId(),
+        joinPoint.getTarget(),
+        compensable.compensationMethod(),
+        joinPoint.getArgs());
 
     preIntercept(joinPoint);
     Object result = joinPoint.proceed();
diff --git 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java
similarity index 62%
copy from 
omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
copy to 
omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java
index 1d17c6c..bb86666 100644
--- 
a/omega/omega-spring-tx/src/test/java/io/servicecomb/saga/omega/transaction/spring/TransactionalUserService.java
+++ 
b/omega/omega-transaction/src/main/java/io/servicecomb/saga/omega/transaction/annotations/Compensable.java
@@ -15,24 +15,15 @@
  * limitations under the License.
  */
 
-package io.servicecomb.saga.omega.transaction.spring;
+package io.servicecomb.saga.omega.transaction.annotations;
 
-import javax.transaction.Transactional;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-@Component
-class TransactionalUserService {
-  private final UserRepository userRepository;
-
-  @Autowired
-  TransactionalUserService(UserRepository userRepository) {
-    this.userRepository = userRepository;
-  }
-
-  @Transactional
-  void add(User user) {
-    userRepository.save(user);
-  }
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Compensable {
+  String compensationMethod();
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@servicecomb.apache.org" <commits@servicecomb.apache.org>.

Reply via email to