use of io.narayana.lra.annotation.Leave in project narayana by jbosstm.
the class NarayanaLRAClient method getTerminationUris.
/**
* For particular compensator class it returns termination uris based on the provided base uri.
* You get map of string and url.
*
* @param compensatorClass compensator class to examine
* @param baseUri base url used on creation of the termination map.
* @return map of urls
*/
public static Map<String, String> getTerminationUris(Class<?> compensatorClass, URI baseUri) {
Map<String, String> paths = new HashMap<>();
final boolean[] asyncTermination = { false };
Annotation resourcePathAnnotation = compensatorClass.getAnnotation(Path.class);
String resourcePath = resourcePathAnnotation == null ? "" : ((Path) resourcePathAnnotation).value().replaceAll("^/+", "");
final String uriPrefix = String.format("%s:%s%s", baseUri.getScheme(), baseUri.getSchemeSpecificPart(), resourcePath).replaceAll("/$", "");
Arrays.stream(compensatorClass.getMethods()).forEach(method -> {
Annotation pathAnnotation = method.getAnnotation(Path.class);
if (pathAnnotation != null) {
if (checkMethod(paths, COMPENSATE, (Path) pathAnnotation, method.getAnnotation(Compensate.class), uriPrefix) != 0) {
TimeLimit timeLimit = method.getAnnotation(TimeLimit.class);
if (timeLimit != null)
paths.put(TIMELIMIT_PARAM_NAME, Long.toString(timeLimit.unit().toMillis(timeLimit.limit())));
if (isAsyncCompletion(method))
asyncTermination[0] = true;
}
if (checkMethod(paths, COMPLETE, (Path) pathAnnotation, method.getAnnotation(Complete.class), uriPrefix) != 0) {
if (isAsyncCompletion(method))
asyncTermination[0] = true;
}
checkMethod(paths, STATUS, (Path) pathAnnotation, method.getAnnotation(Status.class), uriPrefix);
checkMethod(paths, FORGET, (Path) pathAnnotation, method.getAnnotation(Forget.class), uriPrefix);
checkMethod(paths, LEAVE, (Path) pathAnnotation, method.getAnnotation(Leave.class), uriPrefix);
}
});
if (asyncTermination[0] && !paths.containsKey(STATUS) && !paths.containsKey(FORGET)) {
LRALogger.i18NLogger.error_asyncTerminationBeanMissStatusAndForget(compensatorClass);
throw new GenericLRAException(Response.Status.BAD_REQUEST.getStatusCode(), "LRA participant class with asynchronous temination but no @Status or @Forget annotations");
}
StringBuilder linkHeaderValue = new StringBuilder();
if (paths.size() != 0) {
paths.forEach((k, v) -> makeLink(linkHeaderValue, null, k, v));
paths.put("Link", linkHeaderValue.toString());
}
return paths;
}
use of io.narayana.lra.annotation.Leave in project narayana by jbosstm.
the class ServerLRAFilter method filter.
// // TODO figure out how to disable the filters for the coordinator since they are required
// private boolean isCoordinator() {
// return resourceInfo.getResourceClass().getName().equals("io.narayana.lra.coordinator.api.Coordinator")
// }
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
MultivaluedMap<String, String> headers = containerRequestContext.getHeaders();
LRA.Type type = null;
Annotation transactional = method.getDeclaredAnnotation(LRA.class);
URL lraId;
URL newLRA = null;
URL suspendedLRA = null;
URL incommingLRA = null;
String recoveryUrl = null;
boolean nested;
boolean isLongRunning = false;
boolean enlist;
if (transactional == null)
transactional = method.getDeclaringClass().getDeclaredAnnotation(LRA.class);
if (transactional != null) {
type = ((LRA) transactional).value();
isLongRunning = ((LRA) transactional).delayClose();
Response.Status.Family[] cancel0nFamily = ((LRA) transactional).cancelOnFamily();
Response.Status[] cancel0n = ((LRA) transactional).cancelOn();
if (((LRA) transactional).terminal())
containerRequestContext.setProperty(TERMINAL_LRA_PROP, Boolean.TRUE);
if (cancel0nFamily.length != 0)
containerRequestContext.setProperty(CANCEL_ON_FAMILY_PROP, cancel0nFamily);
if (cancel0n.length != 0)
containerRequestContext.setProperty(CANCEL_ON_PROP, cancel0n);
}
boolean endAnnotation = method.isAnnotationPresent(Complete.class) || method.isAnnotationPresent(Compensate.class) || method.isAnnotationPresent(Leave.class) || method.isAnnotationPresent(Status.class) || method.isAnnotationPresent(Forget.class);
if (type == null) {
if (!endAnnotation)
Current.clearContext(headers);
// not transactional
return;
}
if (headers.containsKey(LRA_HTTP_HEADER))
// TODO filters for asynchronous JAX-RS motheods should not throw exceptions
incommingLRA = new URL(headers.getFirst(LRA_HTTP_HEADER));
if (endAnnotation && incommingLRA == null)
return;
enlist = ((LRA) transactional).join();
nested = resourceInfo.getResourceMethod().isAnnotationPresent(NestedLRA.class);
switch(type) {
case // a txn must be present
MANDATORY:
checkForTx(type, incommingLRA, true);
if (nested) {
// a new LRA is nested under the incomming LRA
suspendedLRA = incommingLRA;
lraTrace(containerRequestContext, suspendedLRA, "ServerLRAFilter before: MANDATORY start new LRA");
newLRA = lraId = startLRA(incommingLRA, method, getTimeOut(method));
} else {
lraId = incommingLRA;
// txId is not null
resumeTransaction(incommingLRA);
}
break;
case // a txn must not be present
NEVER:
checkForTx(type, incommingLRA, false);
if (nested) {
// nested does not make sense
throw new GenericLRAException(Response.Status.PRECONDITION_FAILED.getStatusCode(), type.name() + " but found Nested annnotation");
}
enlist = false;
lraId = null;
break;
case NOT_SUPPORTED:
if (nested) {
// nested does not make sense
throw new GenericLRAException(Response.Status.PRECONDITION_FAILED.getStatusCode(), type.name() + " but found Nested annnotation");
}
enlist = false;
suspendedLRA = incommingLRA;
lraId = null;
break;
case REQUIRED:
if (incommingLRA != null) {
if (nested) {
// if there is an LRA present nest a new LRA under it
suspendedLRA = incommingLRA;
lraTrace(containerRequestContext, suspendedLRA, "ServerLRAFilter before: REQUIRED start new LRA");
newLRA = lraId = startLRA(incommingLRA, method, getTimeOut(method));
} else {
lraId = incommingLRA;
resumeTransaction(incommingLRA);
}
} else {
lraTrace(containerRequestContext, null, "ServerLRAFilter before: REQUIRED start new LRA");
newLRA = lraId = startLRA(null, method, getTimeOut(method));
}
break;
case REQUIRES_NEW:
// previous = AtomicAction.suspend();
suspendedLRA = incommingLRA;
lraTrace(containerRequestContext, suspendedLRA, "ServerLRAFilter before: REQUIRES_NEW start new LRA");
newLRA = lraId = startLRA(incommingLRA, method, getTimeOut(method));
break;
case SUPPORTS:
lraId = incommingLRA;
if (nested) {
// if there is an LRA present a new LRA is nested under it otherwise a new top level LRA is begun
if (incommingLRA != null)
suspendedLRA = incommingLRA;
lraTrace(containerRequestContext, incommingLRA, "ServerLRAFilter before: SUPPORTS start new LRA");
newLRA = lraId = startLRA(incommingLRA, method, getTimeOut(method));
} else if (incommingLRA != null) {
resumeTransaction(incommingLRA);
}
break;
default:
lraId = incommingLRA;
}
if (lraId == null) {
lraTrace(containerRequestContext, null, "ServerLRAFilter before: removing header");
// the method call needs to run without a transaction
Current.clearContext(headers);
// non transactional
return;
} else {
lraTrace(containerRequestContext, lraId, "ServerLRAFilter before: adding header");
if (lraId.toExternalForm().contains("recovery-coordi"))
lraWarn(containerRequestContext, lraId, "wrong lra id");
}
if (isLongRunning)
newLRA = null;
// store state with the current thread. TODO for the async version use containerRequestContext.setProperty("lra", Current.peek());
// make the current LRA available to the called method
Current.updateLRAContext(lraId, headers);
if (newLRA != null) {
if (suspendedLRA != null)
Current.putState("suspendedLRA", suspendedLRA);
Current.putState("newLRA", newLRA);
}
lraTrace(containerRequestContext, lraId, "ServerLRAFilter before: making LRA available to injected LRAClient");
// make the current LRA available to the called method
lraClient.setCurrentLRA(lraId);
// TODO make sure it is possible to do compensations inside a new LRA
if (!endAnnotation && enlist) {
// don't enlist for methods marked with Compensate, Complete or Leave
URI baseUri = containerRequestContext.getUriInfo().getBaseUri();
Map<String, String> terminateURIs = NarayanaLRAClient.getTerminationUris(resourceInfo.getResourceClass(), baseUri);
String timeLimitStr = terminateURIs.get(NarayanaLRAClient.TIMELIMIT_PARAM_NAME);
long timeLimit = timeLimitStr == null ? NarayanaLRAClient.DEFAULT_TIMEOUT_MILLIS : Long.valueOf(timeLimitStr);
if (terminateURIs.containsKey("Link")) {
try {
recoveryUrl = lraClient.joinLRA(lraId, timeLimit, toURL(terminateURIs.get(COMPENSATE)), toURL(terminateURIs.get(COMPLETE)), toURL(terminateURIs.get(FORGET)), toURL(terminateURIs.get(LEAVE)), toURL(terminateURIs.get(STATUS)), null);
} catch (IllegalLRAStateException e) {
lraTrace(containerRequestContext, lraId, "ServerLRAFilter before: aborting with " + e.getMessage());
throw e;
} catch (WebApplicationException e) {
lraTrace(containerRequestContext, lraId, "ServerLRAFilter before: aborting with " + e.getMessage());
throw new GenericLRAException(lraId, e.getResponse().getStatus(), e.getMessage(), e);
}
headers.putSingle(LRA_HTTP_RECOVERY_HEADER, recoveryUrl);
} else {
lraTrace(containerRequestContext, lraId, "ServerLRAFilter: skipping resource " + method.getDeclaringClass().getName() + " - no participant annotations");
}
}
if (method.isAnnotationPresent(Leave.class)) {
// leave the LRA
String compensatorId = getCompensatorId(lraId, containerRequestContext.getUriInfo().getBaseUri());
lraTrace(containerRequestContext, lraId, "leaving LRA");
lraClient.leaveLRA(lraId, compensatorId);
// let the participant know which lra he left by leaving the header intact
}
lraTrace(containerRequestContext, lraId, "ServerLRAFilter before: making LRA available as a thread local");
}
use of io.narayana.lra.annotation.Leave in project narayana by jbosstm.
the class LraAnnotationProcessingExtension method processLraAnnotatedType.
<X> void processLraAnnotatedType(@Observes @WithAnnotations({ LRA.class }) ProcessAnnotatedType<X> classAnnotatedWithLra) {
// All compulsory LRA annotations are available at the class
Supplier<Stream<AnnotatedMethod<? super X>>> sup = () -> classAnnotatedWithLra.getAnnotatedType().getMethods().stream();
Set<Class<? extends Annotation>> missing = new HashSet<>();
if (!sup.get().anyMatch(m -> m.isAnnotationPresent(Compensate.class)))
missing.add(Compensate.class);
// gathering all LRA annotations in the class
List<LRA> lraAnnotations = new ArrayList<>();
LRA classLraAnnotation = classAnnotatedWithLra.getAnnotatedType().getAnnotation(LRA.class);
if (classLraAnnotation != null)
lraAnnotations.add(classLraAnnotation);
List<LRA> methodlraAnnotations = sup.get().filter(m -> m.isAnnotationPresent(LRA.class)).map(m -> m.getAnnotation(LRA.class)).collect(Collectors.toList());
lraAnnotations.addAll(methodlraAnnotations);
// when LRA annotations expect no context then they are not part of the LRA and no handling
// of the completion or compensation is needed
boolean isNoLRAContext = lraAnnotations.stream().allMatch(lraAnn -> (lraAnn.value() == LRA.Type.NEVER || lraAnn.value() == LRA.Type.NOT_SUPPORTED));
if (isNoLRAContext)
return;
// if any of the LRA annotations have set the join attribute to true (join to be true means
// handling it as full LRA participant which needs to be completed, compensated...)
// then have to process checks for compulsory LRA annotations
boolean isJoin = lraAnnotations.stream().anyMatch(lraAnn -> lraAnn.join());
final String classAnnotatedWithLraName = classAnnotatedWithLra.getAnnotatedType().getJavaClass().getName();
if (!missing.isEmpty() && isJoin) {
FailureCatalog.INSTANCE.add("Class " + classAnnotatedWithLraName + " uses " + LRA.class.getName() + " which requires methods handling LRA events. Missing annotations in the class: " + missing);
}
// Only one of each LRA annotation is placed in the class
List<AnnotatedMethod<? super X>> methodsWithCompensate = sup.get().filter(m -> m.isAnnotationPresent(Compensate.class)).collect(Collectors.toList());
List<AnnotatedMethod<? super X>> methodsWithComplete = sup.get().filter(m -> m.isAnnotationPresent(Complete.class)).collect(Collectors.toList());
List<AnnotatedMethod<? super X>> methodsWithStatus = sup.get().filter(m -> m.isAnnotationPresent(Status.class)).collect(Collectors.toList());
List<AnnotatedMethod<? super X>> methodsWithLeave = sup.get().filter(m -> m.isAnnotationPresent(Leave.class)).collect(Collectors.toList());
List<AnnotatedMethod<? super X>> methodsWithForget = sup.get().filter(m -> m.isAnnotationPresent(Forget.class)).collect(Collectors.toList());
BiFunction<Class<?>, List<AnnotatedMethod<? super X>>, String> errorMsg = (clazz, methods) -> String.format("There are used multiple annotations '%s' in the class '%s' on methods %s. Only one per the class is expected.", clazz.getName(), classAnnotatedWithLraName, methods.stream().map(a -> a.getJavaMember().getName()).collect(Collectors.toList()));
if (methodsWithCompensate.size() > 1) {
FailureCatalog.INSTANCE.add(errorMsg.apply(Compensate.class, methodsWithCompensate));
}
if (methodsWithComplete.size() > 1) {
FailureCatalog.INSTANCE.add(errorMsg.apply(Complete.class, methodsWithComplete));
}
if (methodsWithStatus.size() > 1) {
FailureCatalog.INSTANCE.add(errorMsg.apply(Status.class, methodsWithStatus));
}
if (methodsWithLeave.size() > 1) {
FailureCatalog.INSTANCE.add(errorMsg.apply(Leave.class, methodsWithLeave));
}
if (methodsWithForget.size() > 1) {
FailureCatalog.INSTANCE.add(errorMsg.apply(Forget.class, methodsWithForget));
}
if (methodsWithCompensate.size() > 0) {
// Each method annotated with LRA-style annotations contain all necessary REST annotations
// @Compensate - requires @Path and @PUT
final AnnotatedMethod<? super X> methodWithCompensate = methodsWithCompensate.get(0);
Function<Class<?>, String> getCompensateMissingErrMsg = (wrongAnnotation) -> getMissingAnnotationError(methodWithCompensate, classAnnotatedWithLra, Compensate.class, wrongAnnotation);
boolean isCompensateContainsPathAnnotation = methodWithCompensate.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(Path.class));
if (!isCompensateContainsPathAnnotation) {
FailureCatalog.INSTANCE.add(getCompensateMissingErrMsg.apply(Path.class));
}
boolean isCompensateContainsPutAnnotation = methodWithCompensate.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(PUT.class));
if (!isCompensateContainsPutAnnotation) {
FailureCatalog.INSTANCE.add(getCompensateMissingErrMsg.apply(PUT.class));
}
boolean isCompensateParametersContainsSuspended = methodWithCompensate.getParameters().stream().flatMap(p -> p.getAnnotations().stream()).anyMatch(a -> a.annotationType().equals(Suspended.class));
if (isCompensateParametersContainsSuspended) {
if (methodsWithStatus.size() == 0 || methodsWithForget.size() == 0) {
FailureCatalog.INSTANCE.add(getMissingAnnotationsForAsynchHandling(methodWithCompensate, classAnnotatedWithLra, Compensate.class));
}
}
}
if (methodsWithComplete.size() > 0) {
// @Complete - requires @Path and @PUT
final AnnotatedMethod<? super X> methodWithComplete = methodsWithComplete.get(0);
Function<Class<?>, String> getCompleteMissingErrMsg = (wrongAnnotation) -> getMissingAnnotationError(methodWithComplete, classAnnotatedWithLra, Complete.class, wrongAnnotation);
boolean isCompleteContainsPathAnnotation = methodWithComplete.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(Path.class));
if (!isCompleteContainsPathAnnotation) {
FailureCatalog.INSTANCE.add(getCompleteMissingErrMsg.apply(Path.class));
}
boolean isCompleteContainsPutAnnotation = methodWithComplete.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(PUT.class));
if (!isCompleteContainsPutAnnotation) {
FailureCatalog.INSTANCE.add(getCompleteMissingErrMsg.apply(PUT.class));
}
boolean isCompleteParametersContainsSuspended = methodWithComplete.getParameters().stream().flatMap(p -> p.getAnnotations().stream()).anyMatch(a -> a.annotationType().equals(Suspended.class));
if (isCompleteParametersContainsSuspended) {
if (methodsWithStatus.size() == 0 || methodsWithForget.size() == 0) {
FailureCatalog.INSTANCE.add(getMissingAnnotationsForAsynchHandling(methodWithComplete, classAnnotatedWithLra, Complete.class));
}
}
}
if (methodsWithStatus.size() > 0) {
// @Status - requires @Path and @GET
final AnnotatedMethod<? super X> methodWithStatus = methodsWithStatus.get(0);
Function<Class<?>, String> getStatusMissingErrMsg = (wrongAnnotation) -> getMissingAnnotationError(methodWithStatus, classAnnotatedWithLra, Status.class, wrongAnnotation);
boolean isStatusContainsPathAnnotation = methodWithStatus.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(Path.class));
if (!isStatusContainsPathAnnotation) {
FailureCatalog.INSTANCE.add(getStatusMissingErrMsg.apply(Path.class));
}
boolean isStatusContainsGetAnnotation = methodWithStatus.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(GET.class));
if (!isStatusContainsGetAnnotation) {
FailureCatalog.INSTANCE.add(getStatusMissingErrMsg.apply(GET.class));
}
}
if (methodsWithLeave.size() > 0) {
// @Leave - requires @PUT
final AnnotatedMethod<? super X> methodWithLeave = methodsWithLeave.get(0);
boolean isLeaveContainsPutAnnotation = methodWithLeave.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(PUT.class));
if (!isLeaveContainsPutAnnotation) {
FailureCatalog.INSTANCE.add(getMissingAnnotationError(methodWithLeave, classAnnotatedWithLra, Leave.class, PUT.class));
}
}
if (methodsWithForget.size() > 0) {
// @Forget - requires @DELETE
final AnnotatedMethod<? super X> methodWithForget = methodsWithForget.get(0);
boolean isForgetContainsPutAnnotation = methodWithForget.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(DELETE.class));
if (!isForgetContainsPutAnnotation) {
FailureCatalog.INSTANCE.add(getMissingAnnotationError(methodWithForget, classAnnotatedWithLra, Forget.class, DELETE.class));
}
}
}
Aggregations