diff --git a/jpos/src/main/java/org/jpos/annotation/Abort.java b/jpos/src/main/java/org/jpos/annotation/Abort.java new file mode 100644 index 0000000000..b104bad9fd --- /dev/null +++ b/jpos/src/main/java/org/jpos/annotation/Abort.java @@ -0,0 +1,24 @@ +package org.jpos.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jpos.transaction.TransactionConstants; +import org.jpos.transaction.TransactionParticipant; + +/** + * Indicates that the annotated method is called in the preparation phase of transaction + * processing, specifying the expected outcome through the {@code result} attribute. + * This replaces the {@link TransactionParticipant} prepare method. + * + * @see {@linkplain https://marqeta.atlassian.net/wiki/spaces/~62f54a31d49df231b62a575d/blog/2023/12/01/3041525965/AutoWiring+Participants+with+jPos+-+Part+I} + * + * Usage: Used in conjunction with {@link AnnotatedParticipant} annotations. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Abort { +} diff --git a/jpos/src/main/java/org/jpos/annotation/Commit.java b/jpos/src/main/java/org/jpos/annotation/Commit.java new file mode 100644 index 0000000000..f67af62d1e --- /dev/null +++ b/jpos/src/main/java/org/jpos/annotation/Commit.java @@ -0,0 +1,24 @@ +package org.jpos.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jpos.transaction.TransactionConstants; +import org.jpos.transaction.TransactionParticipant; + +/** + * Indicates that the annotated method is called in the preparation phase of transaction + * processing, specifying the expected outcome through the {@code result} attribute. + * This replaces the {@link TransactionParticipant} prepare method. + * + * @see {@linkplain https://marqeta.atlassian.net/wiki/spaces/~62f54a31d49df231b62a575d/blog/2023/12/01/3041525965/AutoWiring+Participants+with+jPos+-+Part+I} + * + * Usage: Used in conjunction with {@link AnnotatedParticipant} annotations. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Commit { +} diff --git a/jpos/src/main/java/org/jpos/annotation/PrepareForAbort.java b/jpos/src/main/java/org/jpos/annotation/PrepareForAbort.java new file mode 100644 index 0000000000..0bfadb74a1 --- /dev/null +++ b/jpos/src/main/java/org/jpos/annotation/PrepareForAbort.java @@ -0,0 +1,25 @@ +package org.jpos.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jpos.transaction.TransactionConstants; +import org.jpos.transaction.TransactionParticipant; + +/** + * Indicates that the annotated method is called in the preparation phase of transaction + * processing, specifying the expected outcome through the {@code result} attribute. + * This replaces the {@link TransactionParticipant} prepare method. + * + * @see {@linkplain https://marqeta.atlassian.net/wiki/spaces/~62f54a31d49df231b62a575d/blog/2023/12/01/3041525965/AutoWiring+Participants+with+jPos+-+Part+I} + * + * Usage: Used in conjunction with {@link AnnotatedParticipant} annotations. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PrepareForAbort { + public int result() default TransactionConstants.PREPARED; +} diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/exception/GenericExceptionHandlerProvider.java b/jpos/src/main/java/org/jpos/annotation/resolvers/exception/GenericExceptionHandlerProvider.java index adc9240f86..d99e6791eb 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/exception/GenericExceptionHandlerProvider.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/exception/GenericExceptionHandlerProvider.java @@ -16,7 +16,7 @@ public boolean isMatch(Throwable e) { } @Override - public int doReturn(TransactionParticipant p, Context ctx, Throwable t) { + public int doReturn(Object p, Context ctx, Throwable t) { ctx.log("prepare exception in " + this.getClass().getName()); ctx.log(t); setResultCode(ctx, CMF.INTERNAL_ERROR); diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/exception/ReturnExceptionHandler.java b/jpos/src/main/java/org/jpos/annotation/resolvers/exception/ReturnExceptionHandler.java index c9ace8c39f..27f90928cc 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/exception/ReturnExceptionHandler.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/exception/ReturnExceptionHandler.java @@ -9,7 +9,7 @@ public interface ReturnExceptionHandler { boolean isMatch(Throwable e); - int doReturn(TransactionParticipant p, Context ctx, Throwable obj); + int doReturn(Object p, Context ctx, Throwable obj); default void configure(Method m) {} diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextPassThruResolver.java b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextPassThruResolver.java index 6378fd6923..9b37a8e1c5 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextPassThruResolver.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextPassThruResolver.java @@ -40,7 +40,7 @@ public void configure(Parameter f) throws ConfigurationException { } } @Override - public T getValue(TransactionParticipant participant, Context ctx) { + public T getValue(Object participant, Context ctx) { return (T) new ContextView(ctx, readWrite, readOnly, writeOnly); } @@ -48,7 +48,7 @@ public T getValue(TransactionParticipant participant, Context ctx) { } else { return new Resolver() { @Override - public T getValue(TransactionParticipant participant, Context ctx) { + public T getValue(Object participant, Context ctx) { return (T) ctx; } }; diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextResolver.java b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextResolver.java index 63cbcfce05..5eed7857fc 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextResolver.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/ContextResolver.java @@ -25,7 +25,7 @@ public void configure(Parameter f) { } @Override - public T getValue(TransactionParticipant participant, Context ctx) { + public T getValue(Object participant, Context ctx) { return ctx.get(ctxKey); } }; diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/RegistryResolver.java b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/RegistryResolver.java index a3d9e0ba4e..79119686d6 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/RegistryResolver.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/RegistryResolver.java @@ -28,7 +28,7 @@ public void configure(Parameter f) throws ConfigurationException { } @Override - public T getValue(TransactionParticipant participant, Context ctx) { + public T getValue(Object participant, Context ctx) { return NameRegistrar.getIfExists(registryKey); } diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/Resolver.java b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/Resolver.java index 8625cea29d..234ec4b9d1 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/Resolver.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/parameters/Resolver.java @@ -9,5 +9,5 @@ public interface Resolver { default void configure(Parameter f) throws ConfigurationException { } - T getValue(TransactionParticipant participant, Context ctx); + T getValue(Object participant, Context ctx); } diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/response/MultiValueContextReturnHandler.java b/jpos/src/main/java/org/jpos/annotation/resolvers/response/MultiValueContextReturnHandler.java index a232608f18..171a66b659 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/response/MultiValueContextReturnHandler.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/response/MultiValueContextReturnHandler.java @@ -21,7 +21,6 @@ public boolean isMatch(Method m) { public ReturnHandler resolve(Method m) { Return r = m.getAnnotation(Return.class); final String[] keys = r.value(); - final int jPosRes = m.getAnnotation(Prepare.class).result(); return (participant, ctx, res) -> { Map resMap = (Map) res; for(String key: keys) { @@ -29,7 +28,7 @@ public ReturnHandler resolve(Method m) { ctx.put(key, resMap.get(key)); } } - return jPosRes; + return getJPosResult(m); }; } } \ No newline at end of file diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandler.java b/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandler.java index 052adc2fdc..64d6516ceb 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandler.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandler.java @@ -6,7 +6,7 @@ import org.jpos.transaction.TransactionParticipant; public interface ReturnHandler { - int doReturn(TransactionParticipant p, Context ctx, Object obj); + int doReturn(Object p, Context ctx, Object obj); default void configure(Method m) {} } diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandlerProvider.java b/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandlerProvider.java index 8fa19814d8..0850bf83c2 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandlerProvider.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/response/ReturnHandlerProvider.java @@ -2,9 +2,22 @@ import java.lang.reflect.Method; +import org.jpos.annotation.Prepare; +import org.jpos.annotation.PrepareForAbort; import org.jpos.annotation.resolvers.Priority; public interface ReturnHandlerProvider extends Priority { boolean isMatch(Method m); + ReturnHandler resolve(Method m); + + default int getJPosResult(Method m) { + int jPosRes = 0; + if (m.isAnnotationPresent(Prepare.class)) { + jPosRes = m.getAnnotation(Prepare.class).result(); + } else if (m.isAnnotationPresent(PrepareForAbort.class)) { + jPosRes = m.getAnnotation(PrepareForAbort.class).result(); + } + return jPosRes; + } } \ No newline at end of file diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/response/SingleValueContextReturnHandler.java b/jpos/src/main/java/org/jpos/annotation/resolvers/response/SingleValueContextReturnHandler.java index 3c9050400c..a8b36f051b 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/response/SingleValueContextReturnHandler.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/response/SingleValueContextReturnHandler.java @@ -20,12 +20,11 @@ public boolean isMatch(Method m) { public ReturnHandler resolve(Method m) { Return r = m.getAnnotation(Return.class); final String key = r.value()[0]; - final int jPosRes = m.getAnnotation(Prepare.class).result(); return (participant, ctx, res) -> { if (res != null) { ctx.put(key, res); } - return jPosRes; + return getJPosResult(m); }; } } \ No newline at end of file diff --git a/jpos/src/main/java/org/jpos/annotation/resolvers/response/VoidContextReturnHandler.java b/jpos/src/main/java/org/jpos/annotation/resolvers/response/VoidContextReturnHandler.java index 250a97d626..ef1bf4e830 100644 --- a/jpos/src/main/java/org/jpos/annotation/resolvers/response/VoidContextReturnHandler.java +++ b/jpos/src/main/java/org/jpos/annotation/resolvers/response/VoidContextReturnHandler.java @@ -14,7 +14,6 @@ public boolean isMatch(Method m) { @Override public ReturnHandler resolve(Method m) { - final int jPosRes = m.getAnnotation(Prepare.class).result(); - return (participant, ctx, res) -> jPosRes; + return (participant, ctx, res) -> getJPosResult(m); } } \ No newline at end of file diff --git a/jpos/src/main/java/org/jpos/transaction/AnnotatedParticipantWrapper.java b/jpos/src/main/java/org/jpos/transaction/AnnotatedParticipantWrapper.java index 7873bc52b2..2d260c28b4 100644 --- a/jpos/src/main/java/org/jpos/transaction/AnnotatedParticipantWrapper.java +++ b/jpos/src/main/java/org/jpos/transaction/AnnotatedParticipantWrapper.java @@ -1,19 +1,28 @@ package org.jpos.transaction; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; import java.util.List; +import org.jpos.annotation.Abort; import org.jpos.annotation.AnnotatedParticipant; +import org.jpos.annotation.Commit; import org.jpos.annotation.Prepare; +import org.jpos.annotation.PrepareForAbort; import org.jpos.annotation.resolvers.ResolverFactory; import org.jpos.annotation.resolvers.exception.ReturnExceptionHandler; import org.jpos.annotation.resolvers.parameters.Resolver; import org.jpos.annotation.resolvers.response.ReturnHandler; import org.jpos.core.ConfigurationException; +import org.jpos.transaction.AnnotatedParticipantWrapper.AnnotatedMethodType; +import org.jpos.transaction.AnnotatedParticipantWrapper.MethodData; +import org.jpos.transaction.AnnotatedParticipantWrapper.TransactionParticipantHandler; import net.bytebuddy.ByteBuddy; import net.bytebuddy.implementation.MethodDelegation; @@ -23,22 +32,109 @@ import net.bytebuddy.matcher.ElementMatchers; public class AnnotatedParticipantWrapper { - - protected TransactionParticipant participant; - protected Method prepare; + enum AnnotatedMethodType { + PREPARE(Prepare.class, "prepare", "doPrepare"), + COMMIT(Commit.class, "commit"), + ABORT(Abort.class, "abort"), + PREPARE_FOR_ABORT(PrepareForAbort.class, "prepareForAbort"); + + Class annotation; + List methodNames = new ArrayList<>(); + + AnnotatedMethodType(Class annotation, String... methodNames) { + this.annotation = annotation; + for (String name : methodNames) { + this.methodNames.add(name); + } + } + + Class getAnnotation() { + return annotation; + } + } - List args = new ArrayList<>(); - ReturnHandler returnHandler; - List exceptionHandlers; - Method checkPoint; + class MethodData { + AnnotatedMethodType type; + Method method; + List args = new ArrayList<>(); + ReturnHandler returnHandler; + List exceptionHandlers; + + public boolean isMatch(Method method) { + for (String name : type.methodNames) { + if (method.getName().equals(name) + && method.getParameterCount() == 2 + && long.class.isAssignableFrom(method.getParameterTypes()[0]) + && Serializable.class.isAssignableFrom(method.getParameterTypes()[1])) { + return true; + } + } + return false; + } + + int execute(long id, Serializable o) { + Context ctx = (Context) o; + try { + Object[] resolvedArgs = new Object[args.size()]; + int i = 0; + for(Resolver r: args) { + resolvedArgs[i++] = r.getValue(participant, ctx); + } + Object res = method.invoke(participant, resolvedArgs); + + return returnHandler.doReturn(participant, ctx, res); + } catch (IllegalAccessException | IllegalArgumentException e) { + return processException(ctx, e); + } catch (InvocationTargetException e) { + return processException(ctx, e.getTargetException()); + } + } + + private int processException(Context ctx, Throwable e) { + ctx.log("Failed to execute " + method.toString()); + ctx.log(e); + for(ReturnExceptionHandler handler: exceptionHandlers) { + if (handler.isMatch(e)) { + return handler.doReturn(participant, ctx, e); + } + } + throw new RuntimeException(e); + } + } - public static T wrap(T participant) throws ConfigurationException { + protected Object participant; + protected EnumMap methods = new EnumMap<>(AnnotatedMethodType.class); + + /** + * Wraps the given {@link TransactionParticipant} object with an {@link AnnotatedParticipantWrapper}, + * enabling dynamic method invocation based on annotations. This method is designed to enhance objects + * marked with {@link AnnotatedParticipant} by providing additional functionalities + * such as customized prepared method handling. + * + * Note that the return object is a pass thru subclass of the original participant. But while the methods + * are passed thru to the underlying object, if you access field variables directly, you will be accessing + * the subclass field variables and not the original participant. For this reason, if the wrapped object is + * used directly, only access methods. + * + * @param obj the object to wrap, typically an instance of a class annotated with + * {@link AnnotatedParticipant}. + * @return an instance of {@link AnnotatedParticipantWrapper} that wraps the provided + * object, enriched with dynamic annotation processing capabilities. + */ + @SuppressWarnings("unchecked") + public static T wrap(Object participant) throws ConfigurationException { try { AnnotatedParticipantWrapper handler = new AnnotatedParticipantWrapper(participant); + List interfaces = new ArrayList<>(); + interfaces.add(TransactionParticipant.class); + if (handler.methods.containsKey(AnnotatedMethodType.PREPARE_FOR_ABORT)) { + interfaces.add(AbortParticipant.class); + } return (T) new ByteBuddy() .subclass(participant.getClass()) + .implement(interfaces.toArray(new Class[0])) .method(ElementMatchers.any()) - .intercept(MethodDelegation.to(handler)) + .intercept(MethodDelegation.to(new TransactionParticipantHandler(handler))) .make() .load(participant.getClass().getClassLoader()) .getLoaded() @@ -49,78 +145,78 @@ public static T wrap(T participant) throws Co } } - public static boolean isMatch(TransactionParticipant participant) { + public static boolean isMatch(Object participant) { return participant.getClass().isAnnotationPresent(AnnotatedParticipant.class); } - public AnnotatedParticipantWrapper(TransactionParticipant participant) throws ConfigurationException { + public AnnotatedParticipantWrapper(Object participant) throws ConfigurationException { this.participant = participant; - configurePrepareMethod(); - configureReturnHandler(); - configureParameters(); - + configureAnnotatedMethods(); } - protected void configureReturnHandler() throws ConfigurationException { - returnHandler = ResolverFactory.INSTANCE.getReturnHandler(prepare); - exceptionHandlers = ResolverFactory.INSTANCE.getExceptionHandlers(prepare); - } - protected void configureParameters() throws ConfigurationException { - for(Parameter p: prepare.getParameters()) { + protected List configureParameters(Method m) throws ConfigurationException { + List args = new ArrayList<>(); + for(Parameter p: m.getParameters()) { args.add(ResolverFactory.INSTANCE.getResolver(p)); } + return args; } - protected void configurePrepareMethod() throws ConfigurationException { + protected void configureAnnotatedMethods() throws ConfigurationException { for(Method m: participant.getClass().getMethods()) { - if (m.isAnnotationPresent(Prepare.class)) { - if (prepare == null) { - prepare = m; - } else { - throw new ConfigurationException("Only one method per class can be defined with the @Prepare. " + participant.getClass().getSimpleName() + " has multiple matches."); + for (AnnotatedMethodType type : AnnotatedMethodType.values()) { + if (m.isAnnotationPresent(type.getAnnotation())) { + if (methods.containsKey(type)) { + throw new ConfigurationException("Only one method per class can be defined with the @" + + type.getAnnotation().getSimpleName() + ". " + participant.getClass().getSimpleName() + + " has multiple matches."); + } + MethodData data = new MethodData(); + data.type = type; + data.method = m; + data.exceptionHandlers = ResolverFactory.INSTANCE.getExceptionHandlers(m); + data.returnHandler = ResolverFactory.INSTANCE.getReturnHandler(m); + data.args = configureParameters(m); + methods.put(type, data); } } } - if (prepare == null) { + + if (methods.isEmpty()) { throw new ConfigurationException(participant.getClass().getSimpleName() + " needs one method defined with the @Prepare annotation."); } - } - - - public final int prepare(long id, Serializable o) { - Context ctx = (Context) o; - try { - Object[] resolvedArgs = new Object[args.size()]; - int i = 0; - for(Resolver r: args) { - resolvedArgs[i++] = r.getValue(participant, ctx); - } - Object res = prepare.invoke(participant, resolvedArgs); - - return returnHandler.doReturn(participant, ctx, res); - } catch (IllegalAccessException | IllegalArgumentException e) { - return processException(ctx, e); - } catch (InvocationTargetException e) { - return processException(ctx, e.getTargetException()); + if (!(participant instanceof TransactionParticipant) && + !methods.keySet().containsAll( + Arrays.asList(AnnotatedMethodType.PREPARE, AnnotatedMethodType.COMMIT, AnnotatedMethodType.ABORT))) { + throw new ConfigurationException( + participant.getClass().getSimpleName() + " needs to define all of the @Prepare, @Commit, and @Abort annotations or implement TransactionParticipant."); } } - - private int processException(Context ctx, Throwable e) { - ctx.log("Failed to execute " + prepare.toString()); - ctx.log(e); - for(ReturnExceptionHandler handler: exceptionHandlers) { - if (handler.isMatch(e)) { - return handler.doReturn(participant, ctx, e); + + public static class TransactionParticipantHandler { + + AnnotatedParticipantWrapper parent; + + private TransactionParticipantHandler(AnnotatedParticipantWrapper parent) { + this.parent = parent; + } + + @RuntimeType + public Object intercept(@Origin Method method, @AllArguments @RuntimeType Object[] args) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + for (MethodData data : parent.methods.values()) { + if (data.isMatch(method)) { + int res = data.execute((long) args[0], (Serializable) args[1]); + if (method.getReturnType().equals(void.class)) { + return null; + } else { + return res; + } + } } + return method.invoke(parent.participant, args); } - throw new RuntimeException(e); - } - - @RuntimeType - public Object intercept(@AllArguments Object[] args, - @Origin Method method) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - return method.invoke(participant, args); } } diff --git a/jpos/src/main/java/org/jpos/transaction/TransactionManager.java b/jpos/src/main/java/org/jpos/transaction/TransactionManager.java index 58d3be8cdc..b20029db9f 100644 --- a/jpos/src/main/java/org/jpos/transaction/TransactionManager.java +++ b/jpos/src/main/java/org/jpos/transaction/TransactionManager.java @@ -805,7 +805,7 @@ public TransactionParticipant createParticipant (Element e) throws ConfigurationException { QFactory factory = getFactory(); - TransactionParticipant participant = + Object participant = factory.newInstance (QFactory.getAttributeValue (e, "class") ); factory.setLogger (participant, e); @@ -816,14 +816,17 @@ public TransactionParticipant createParticipant (Element e) realm = ":" + realm; else realm = ""; - names.put(participant, Caller.shortClassName(participant.getClass().getName())+realm); if (participant instanceof Destroyable) { destroyables.add((Destroyable) participant); } if (AnnotatedParticipantWrapper.isMatch(participant)) { participant = AnnotatedParticipantWrapper.wrap(participant); } - return participant; + TransactionParticipant rParticipant = (TransactionParticipant) participant; + + names.put(rParticipant, Caller.shortClassName(participant.getClass().getName())+realm); + + return rParticipant; } @Override diff --git a/jpos/src/test/java/org/jpos/transaction/AnnotatedParticipantWrapperTest.java b/jpos/src/test/java/org/jpos/transaction/AnnotatedParticipantWrapperTest.java index dc8dd80bb1..3b61a7cdc3 100644 --- a/jpos/src/test/java/org/jpos/transaction/AnnotatedParticipantWrapperTest.java +++ b/jpos/src/test/java/org/jpos/transaction/AnnotatedParticipantWrapperTest.java @@ -28,6 +28,8 @@ import org.jpos.annotation.ContextKey; import org.jpos.annotation.ContextKeys; import org.jpos.annotation.Prepare; +import org.jpos.annotation.Commit; +import org.jpos.annotation.Abort; import org.jpos.annotation.Registry; import org.jpos.annotation.Return; import org.jpos.core.Configuration; @@ -50,7 +52,11 @@ public class AnnotatedParticipantWrapperTest { private static final String HANDLER = "handler"; public static class TxnSupport implements TransactionParticipant { - public int prepare(long id, Serializable context) { + public final int prepare(long id, Serializable context) { + return doPrepare(id, (Context) context); + } + + public int doPrepare(long id, Context ctx) { return TransactionConstants.ABORTED; } @@ -68,6 +74,38 @@ public Object getCard(Context ctx, @ContextKey("DB") Object db, @Registry Long s return new Object(); } } + + @AnnotatedParticipant + public static class AnnotatedParticipantNoTxnSupport { + @Prepare(result = TransactionConstants.PREPARED | TransactionConstants.READONLY) + @Return("CARD") + public Object getCard(Context ctx, @ContextKey("DB") Object db, @Registry Long someKey) throws Exception { + Assertions.assertNotNull(ctx); + Assertions.assertNotNull(db); + Assertions.assertEquals(2, someKey); + return new Object(); + } + + public void setConfiguration(Configuration cfg) {} + + @Abort + @Return("CARD") + public Object getCardAbort(Context ctx, @ContextKey("DB") Object db, @Registry Long someKey) throws Exception { + Assertions.assertNotNull(ctx); + Assertions.assertNotNull(db); + Assertions.assertEquals(2, someKey); + return new Object(); + } + + @Commit + @Return("CARD") + public Object getCardCommit(Context ctx, @ContextKey("DB") Object db, @Registry Long someKey) throws Exception { + Assertions.assertNotNull(ctx); + Assertions.assertNotNull(db); + Assertions.assertEquals(2, someKey); + return new Object(); + } + } public static class RegularParticipantTest extends TxnSupport {} @@ -93,6 +131,15 @@ public void testClassAnnotation() throws ConfigurationException { TxnSupport pw = AnnotatedParticipantWrapper.wrap(p); assertAnnotatedParticipant(ctx, pw); + // Test wrapper annotation + AnnotatedParticipantNoTxnSupport pwn = new AnnotatedParticipantNoTxnSupport(); + pwn.setConfiguration(cfg); + assertTrue(AnnotatedParticipantWrapper.isMatch(pwn)); + TransactionParticipant pt = AnnotatedParticipantWrapper.wrap(pwn); + assertAnnotatedParticipant(ctx, pt); + ctx.remove("CARD"); + pt.commit(0, ctx); + assertNotNull(ctx.get("CARD")); } @Test @@ -103,22 +150,28 @@ public void testClassAnnotationFromTxnMgr() throws ConfigurationException, JDOME when(q2.getFactory()).thenReturn(f); doReturn(new RegularParticipantTest()).when(f).newInstance(RegularParticipantTest.class.getCanonicalName()); doReturn(new AnnotatedParticipantTest()).when(f).newInstance(AnnotatedParticipantTest.class.getCanonicalName()); + doReturn(new AnnotatedParticipantNoTxnSupport()).when(f).newInstance(AnnotatedParticipantNoTxnSupport.class.getCanonicalName()); txnMgr.setServer(q2); txnMgr.setName("txnMgr"); txnMgr.setConfiguration(new SimpleConfiguration()); String regParticipantXml = ""; String annotatedParticipantXml = ""; + String annotatedParticipantNoTxnSupportXml = ""; Context ctx = new Context(); ctx.put("DB", new Object()); NameRegistrar.register("someKey", 2L); - TxnSupport p = (TxnSupport) getParticipant(txnMgr, regParticipantXml); + TransactionParticipant p = (TxnSupport) getParticipant(txnMgr, regParticipantXml); assertRegularParticipant(ctx, p, false); - p = (TxnSupport) getParticipant(txnMgr, annotatedParticipantXml); + p = getParticipant(txnMgr, annotatedParticipantXml); + assertAnnotatedParticipant(ctx, p); + + p = getParticipant(txnMgr, annotatedParticipantNoTxnSupportXml); assertAnnotatedParticipant(ctx, p); + } @@ -532,12 +585,13 @@ public void addHandler(Context ctx, Handler handler) { ctx.put(HANDLER, handler); } - protected void assertAnnotatedParticipant(Context ctx, TxnSupport p) { + protected void assertAnnotatedParticipant(Context ctx, TransactionParticipant p) { + ctx.remove("CARD"); assertEquals(TransactionConstants.PREPARED | TransactionConstants.READONLY, p.prepare(0, ctx)); assertNotNull(ctx.get("CARD")); } - protected void assertRegularParticipant(Context ctx, TxnSupport p, boolean ignoreAnnotation) { + protected void assertRegularParticipant(Context ctx, TransactionParticipant p, boolean ignoreAnnotation) { if (!ignoreAnnotation) { assertFalse(AnnotatedParticipantWrapper.isMatch(p)); }