executionErrorEvent;
15 |
16 | /**
17 | * Assembles and fires ExecutionError event.
18 | *
19 | * @param invocationContext Interceptor's invocation context
20 | * @param throwable Throwable catched by the interceptor
21 | */
22 | protected void throwExecutionErrorEvent(InvocationContext invocationContext, Throwable throwable) {
23 | ExecutionErrorEvent executionErrorEvent = new ExecutionErrorEvent(invocationContext.getMethod(),
24 | throwable, LocalDateTime.now());
25 | this.executionErrorEvent.fire(executionErrorEvent);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Pavel Pscheidl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/failsafe/Semisafe.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import javax.enterprise.util.Nonbinding;
4 | import javax.interceptor.InterceptorBinding;
5 | import java.lang.annotation.Inherited;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | import static java.lang.annotation.ElementType.METHOD;
11 | import static java.lang.annotation.ElementType.TYPE;
12 |
13 | /**
14 | * Binds {@link SemisafeInterceptor} to a specific method or to each and every public-declared method when placed on top
15 | * of a CDI bean.
16 | *
17 | * The underlying interceptor lets through every Throwable declared as a value inside this annotation interface, or
18 | * any throwable declared directly by the method itself.
19 | *
20 | * @author Pavel Pscheidl
21 | */
22 | @Inherited
23 | @InterceptorBinding
24 | @Retention(RetentionPolicy.RUNTIME)
25 | @Target({METHOD, TYPE})
26 | public @interface Semisafe {
27 |
28 | /**
29 | * Classes extending {@link Throwable} being ignored in the failsafe process. Such throwables are re-thrown and do
30 | * not trigger the failsafe process.
31 | * @return Ignored exceptions
32 | */
33 | @Nonbinding
34 | Class extends Throwable>[] value() default {};
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/failsafe/DeclaredExceptionFailsafeInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import javax.interceptor.AroundInvoke;
4 | import javax.interceptor.InvocationContext;
5 | import java.util.Optional;
6 |
7 | public class DeclaredExceptionFailsafeInterceptor extends FailsafeInterceptor {
8 |
9 |
10 | private static boolean isExceptionIgnorable(final Class extends Throwable> throwable,
11 | final Class>[] declaredThrowableClasses) {
12 | for (Class> declaredException : declaredThrowableClasses) {
13 | if (throwable.equals(declaredException)) {
14 | return true;
15 | }
16 | }
17 |
18 | return false;
19 | }
20 |
21 | @AroundInvoke
22 | public Object guard(InvocationContext invocationContext) throws Throwable {
23 | try {
24 | Object returnedObject = invocationContext.proceed();
25 |
26 | if (returnedObject == null || !(returnedObject instanceof Optional)) {
27 | return Optional.empty();
28 | }
29 |
30 | return returnedObject;
31 | } catch (Throwable throwable) {
32 | final Class>[] exceptionTypes = invocationContext.getMethod().getExceptionTypes();
33 | if (isExceptionIgnorable(throwable.getClass(), exceptionTypes)) {
34 | throw throwable;
35 | }
36 | super.throwExecutionErrorEvent(invocationContext, throwable);
37 | return Optional.empty();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/failsafe/ExecutionErrorEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import java.lang.reflect.Method;
4 | import java.time.LocalDateTime;
5 |
6 | /**
7 | * An event representing erroneous execution of a method annotated with {@link Failsafe}.
8 | *
9 | * @author Pavel Pscheidl
10 | */
11 | public class ExecutionErrorEvent {
12 |
13 | private final Method calledMethod;
14 | private final Throwable throwable;
15 | private final LocalDateTime failTime;
16 |
17 | /**
18 | * Constructs a new instance of {@link ExecutionErrorEvent}
19 | *
20 | * @param executedMethod The erroneous method executed
21 | * @param throwable Cause of erroneous behavior
22 | * @param failTime Time the non-standard behavior was captured
23 | */
24 | protected ExecutionErrorEvent(Method executedMethod, Throwable throwable, LocalDateTime failTime) {
25 | this.calledMethod = executedMethod;
26 | this.throwable = throwable;
27 | this.failTime = failTime;
28 | }
29 |
30 | /**
31 | * @return Method the erroneous behavior appeared in.
32 | */
33 | public Method getCalledMethod() {
34 | return calledMethod;
35 | }
36 |
37 |
38 | /**
39 | * @return The {@link Throwable} cause of erroneous behavior.
40 | */
41 | public Throwable getThrowable() {
42 | return throwable;
43 | }
44 |
45 |
46 | /**
47 | * @return Time the erroneous method execution was intercepted at.
48 | */
49 | public LocalDateTime getFailTime() {
50 | return failTime;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/com/github/pscheidl/fortee/failsafe/beans/SemiGuardedBean.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe.beans;
2 |
3 | import com.github.pscheidl.fortee.failsafe.Semisafe;
4 | import junit.framework.AssertionFailedError;
5 | import org.apache.commons.lang3.exception.ExceptionUtils;
6 |
7 | import javax.enterprise.context.Dependent;
8 | import java.io.IOException;
9 | import java.util.Optional;
10 |
11 | @Dependent
12 | public class SemiGuardedBean {
13 |
14 | @Semisafe({AssertionError.class})
15 | public Optional letThrough() {
16 | throw new AssertionError();
17 | }
18 |
19 | @Semisafe
20 | public Optional letThroughDeclaration() throws AssertionError {
21 | throw new AssertionError();
22 | }
23 |
24 | @Semisafe({AssertionError.class})
25 | public Optional letInheritedThrough() {
26 | throw new AssertionFailedError();
27 | }
28 |
29 | @Semisafe({AssertionError.class})
30 | public Optional doNotLetThrough() {
31 | throw new ClassFormatError();
32 | }
33 |
34 | @Semisafe
35 | public Optional returnNull() {
36 | return null;
37 | }
38 |
39 | @Semisafe
40 | public Optional returnSomething() {
41 | return Optional.of("Something");
42 | }
43 |
44 | @Semisafe({IOException.class})
45 | public Optional throwSilentException() {
46 | return ExceptionUtils.rethrow(new IOException());
47 | }
48 |
49 | @Semisafe
50 | public Optional convertSilentException() {
51 | return ExceptionUtils.rethrow(new IOException());
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/failsafe/NoExceptionsFailsafeInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import javax.annotation.Priority;
4 | import javax.interceptor.AroundInvoke;
5 | import javax.interceptor.Interceptor;
6 | import javax.interceptor.InvocationContext;
7 | import java.io.Serializable;
8 | import java.util.Optional;
9 |
10 | /**
11 | * Transforms uncatched exceptions into an empty optional. Method with this interceptor present must have the return
12 | * type of Optional
13 | *
14 | * @author Pavel Pscheidl
15 | */
16 | @Interceptor
17 | @Failsafe
18 | @Priority(Interceptor.Priority.LIBRARY_BEFORE)
19 | class NoExceptionsFailsafeInterceptor extends FailsafeInterceptor implements Serializable {
20 |
21 | /**
22 | * If there is an exception thrown in the underlying method call, the exception is converted into an empty
23 | * Optional.
24 | *
25 | * @param invocationContext Interceptor's invocation context
26 | * @return Value returned by the underlying method call. Empty optional in case of an exception.
27 | */
28 | @AroundInvoke
29 | public Object guard(InvocationContext invocationContext) {
30 | try {
31 | Object returnedObject = invocationContext.proceed();
32 |
33 | if (returnedObject == null || !(returnedObject instanceof Optional)) {
34 | return Optional.empty();
35 | }
36 |
37 | return returnedObject;
38 | } catch (Throwable throwable) {
39 | super.throwExecutionErrorEvent(invocationContext, throwable);
40 | return Optional.empty();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/com/github/pscheidl/fortee/failsafe/FailsafeInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import com.github.pscheidl.fortee.failsafe.beans.FailingBean;
4 | import com.github.pscheidl.fortee.failsafe.beans.NotFailingBean;
5 | import org.jboss.arquillian.container.test.api.Deployment;
6 | import org.jboss.arquillian.junit.Arquillian;
7 | import org.jboss.shrinkwrap.api.ShrinkWrap;
8 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
9 | import org.jboss.shrinkwrap.api.spec.WebArchive;
10 | import org.jboss.shrinkwrap.resolver.api.maven.archive.importer.MavenImporter;
11 | import org.junit.Assert;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | import javax.inject.Inject;
16 | import java.util.Optional;
17 |
18 | /**
19 | * @author Pavel Pscheidl
20 | */
21 | @RunWith(Arquillian.class)
22 | public class FailsafeInterceptorTest {
23 |
24 | @Inject
25 | private FailingBean failingBean;
26 |
27 | @Inject
28 | private NotFailingBean notFailingBean;
29 |
30 | @Deployment
31 | public static WebArchive createDeployment() {
32 |
33 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
34 | .loadPomFromFile("pom.xml")
35 | .importBuildOutput()
36 | .as(JavaArchive.class);
37 |
38 | return ShrinkWrap.create(WebArchive.class)
39 | .addAsLibrary(as)
40 | .addClass(FailingBean.class)
41 | .addClass(NotFailingBean.class)
42 | .addAsWebInfResource("beans.xml");
43 | }
44 |
45 | @Test
46 | public void testFailingBean() {
47 | Optional emptyOptional = failingBean.throwError();
48 | Assert.assertNotNull(emptyOptional);
49 | }
50 |
51 | @Test
52 | public void testNotFailingBean() {
53 | Optional optionalWithStringInside = notFailingBean.returnOptionalWithStringInside();
54 | Assert.assertNotNull(optionalWithStringInside);
55 | Assert.assertTrue(optionalWithStringInside.isPresent());
56 | }
57 |
58 | @Test
59 | public void testConvertNull() {
60 | Optional optionalWithStringInside = failingBean.returnNull();
61 | Assert.assertNotNull(optionalWithStringInside);
62 | Assert.assertFalse(optionalWithStringInside.isPresent());
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FortEE
2 |
3 | [](https://travis-ci.org/Pscheidl/FortEE)
4 | [](https://www.codacy.com/app/pavel.junior/FortEE?utm_source=github.com&utm_medium=referral&utm_content=Pscheidl/FortEE&utm_campaign=Badge_Grade)
5 |
6 | FortEE is a Jakarta EE / Java EE fault-tolerance guard leveraging the Optional pattern. Its power lies in its simplicity. On methods returning Optional, a @Failsafe annotation can be placed. Any uncaught exceptional states are then converted into an Optional.empty(). Synchronous or asynchronous invocation is not enforced.
7 |
8 | - Simple and fast
9 | - Startup-time check
10 | - Tiny in size
11 | - Compatible with Jakarta EE 8, requires Java EE 7
12 |
13 |
14 | ## Maven
15 | ```xml
16 |
17 | com.github.pscheidl
18 | fortee
19 | 1.2.0
20 |
21 | ```
22 | ## Gradle
23 | ```groovy
24 | compile 'com.github.pscheidl:fortee:1.2.0'
25 | ```
26 | **Release notes**
27 | - Released on 14th of June 2020
28 | - The `@Semisafe` annotation ignored exceptions not only listed as a value of that annotation,
29 | but also exceptions directly specified in the mehod's definition, e.g. `public Optional doSomething() throws UnsupportedOperationException` will let the
30 | `UnsupportedOperationException` through.
31 | - Test suite ran against WildFly 20.0.0-Final.
32 |
33 | ## How it works ?
34 |
35 | For more information, please visit [FortEE wikipedia](https://github.com/Pscheidl/FortEE/wiki).
36 |
37 | Guard against all checked and unchecked exceptions.
38 |
39 | ```java
40 | @Named
41 | public class ServiceImplementation implements SomeService {
42 |
43 | // Will return Optional.empty()
44 | @Failsafe
45 | public Optional maybeFail(){
46 | throw new RuntimeException("Failed on purpose");
47 | }
48 |
49 | }
50 | ```
51 |
52 | Guard against all exceptions but the declared ones, or the ones listed in the `@Semisafe` annotation..
53 |
54 | ```java
55 | @Named
56 | public class ServiceImplementation implements SomeService {
57 |
58 | // Will end with RuntimeException
59 | @Semisafe
60 | public Optional maybeFail() throws RuntimeException {
61 | throw new RuntimeException("Failed on purpose");
62 | }
63 |
64 | }
65 | ```
66 |
67 | Alternatively, the exception to be let through can be specified inside the `@Semisafe` annotation.
68 |
69 | ```java
70 | @Named
71 | public class ServiceImplementation implements SomeService {
72 |
73 | // Will end with RuntimeException
74 | @Semisafe({RuntimeException.class})
75 | public Optional maybeFail(){
76 | throw new RuntimeException("Failed on purpose");
77 | }
78 |
79 | }
80 | ```
81 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/failsafe/SemisafeInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import javax.annotation.Priority;
4 | import javax.interceptor.AroundInvoke;
5 | import javax.interceptor.Interceptor;
6 | import javax.interceptor.InvocationContext;
7 | import java.io.Serializable;
8 | import java.lang.reflect.Method;
9 | import java.util.Optional;
10 |
11 | /**
12 | * Transforms uncatched exceptions into an empty optional, except for allowed exceptions defined in {@link Semisafe}
13 | * annotation. Method with this interceptor present must have the return type of Optional.
14 | *
15 | * @author Pavel Pscheidl
16 | */
17 | @Interceptor
18 | @Semisafe({})
19 | @Priority(Interceptor.Priority.LIBRARY_BEFORE)
20 | class SemisafeInterceptor extends FailsafeInterceptor implements Serializable {
21 |
22 | /**
23 | * If there is a {@link Throwable} thrown in the underlying method call, the exception is converted into an empty
24 | * Optional, unless the {@link Error} or {@link Exception} is listed as ignorable in {@link Semisafe} annotation.
25 | *
26 | * @param invocationContext Interceptor's invocation context
27 | * @return Value returned by the underlying method call. Empty optional in case of an exception.
28 | */
29 | @AroundInvoke
30 | public Object filter(final InvocationContext invocationContext) throws Throwable {
31 | try {
32 | final Object returnedObject = invocationContext.proceed();
33 |
34 | if (returnedObject == null || !(returnedObject instanceof Optional)) {
35 | return Optional.empty();
36 | }
37 | return returnedObject;
38 | } catch (Throwable throwable) {
39 | if (isIgnoredThrowable(throwable.getClass(), invocationContext.getMethod())) {
40 | throw throwable;
41 | }
42 | super.throwExecutionErrorEvent(invocationContext, throwable);
43 | return Optional.empty();
44 | }
45 | }
46 |
47 | /**
48 | * @param throwableClass Intercepted {@link Throwable}
49 | * @param method Intercepted {@link Method}
50 | * @return True if {@link Throwable} instance is among throwables to be ignored
51 | */
52 | private boolean isIgnoredThrowable(final Class extends Throwable> throwableClass, final Method method) {
53 | Semisafe methodAnnotation = method.getAnnotation(Semisafe.class);
54 |
55 | if (methodAnnotation == null) {
56 | methodAnnotation = method.getDeclaringClass().getAnnotation(Semisafe.class);
57 | }
58 |
59 | for (final Class letThroughClass : methodAnnotation.value()) {
60 | if (letThroughClass.isAssignableFrom(throwableClass)) {
61 | return true;
62 | }
63 | }
64 |
65 | for (Class> declaredException : method.getExceptionTypes()) {
66 | if (throwableClass.isAssignableFrom(declaredException)) {
67 | return true;
68 | }
69 | }
70 | return false;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/java/com/github/pscheidl/fortee/failsafe/SemisafeInterceptorTest.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.failsafe;
2 |
3 | import com.github.pscheidl.fortee.failsafe.beans.SemiGuardedBean;
4 | import org.apache.commons.lang3.exception.ExceptionUtils;
5 | import org.jboss.arquillian.container.test.api.Deployment;
6 | import org.jboss.arquillian.junit.Arquillian;
7 | import org.jboss.shrinkwrap.api.ShrinkWrap;
8 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
9 | import org.jboss.shrinkwrap.api.spec.WebArchive;
10 | import org.jboss.shrinkwrap.resolver.api.maven.archive.importer.MavenImporter;
11 | import org.junit.Assert;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.rules.ExpectedException;
15 | import org.junit.runner.RunWith;
16 |
17 | import javax.inject.Inject;
18 | import java.io.IOException;
19 | import java.util.Optional;
20 |
21 | @RunWith(Arquillian.class)
22 | public class SemisafeInterceptorTest {
23 | @Rule
24 | public ExpectedException expectedException = ExpectedException.none();
25 | @Inject
26 | private SemiGuardedBean semiGuardedBean;
27 |
28 | @Deployment
29 | public static WebArchive createDeployment() {
30 |
31 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
32 | .loadPomFromFile("pom.xml")
33 | .importBuildOutput()
34 | .as(JavaArchive.class);
35 |
36 | return ShrinkWrap.create(WebArchive.class)
37 | .addAsLibrary(as)
38 | .addClass(ExceptionUtils.class)
39 | .addClass(SemiGuardedBean.class)
40 | .addAsWebInfResource("beans.xml");
41 | }
42 |
43 | @Test
44 | public void testLetThrough() {
45 | expectedException.expect(AssertionError.class);
46 | semiGuardedBean.letThrough();
47 | }
48 |
49 | @Test
50 | public void testLetThroughDeclaration() {
51 | expectedException.expect(AssertionError.class);
52 | semiGuardedBean.letThroughDeclaration();
53 | }
54 |
55 | @Test
56 | public void testNotLetThrough() {
57 | final Optional optionalReturnValue = semiGuardedBean.doNotLetThrough();
58 | Assert.assertNotNull(optionalReturnValue);
59 | Assert.assertFalse(optionalReturnValue.isPresent());
60 | }
61 |
62 | @Test
63 | public void testInheritance() {
64 | expectedException.expect(AssertionError.class);
65 | semiGuardedBean.letInheritedThrough();
66 | }
67 |
68 | @Test
69 | public void testConvertNull() {
70 | Optional optionalWithStringInside = semiGuardedBean.returnNull();
71 | Assert.assertNotNull(optionalWithStringInside);
72 | Assert.assertFalse(optionalWithStringInside.isPresent());
73 | }
74 |
75 | @Test
76 | public void testValueReturned() {
77 | Optional optionalWithStringInside = semiGuardedBean.returnSomething();
78 | Assert.assertNotNull(optionalWithStringInside);
79 | Assert.assertTrue(optionalWithStringInside.isPresent());
80 | Assert.assertEquals("Something", optionalWithStringInside.get());
81 | }
82 |
83 | @Test
84 | public void testThrowSilentException() {
85 | expectedException.expect(IOException.class);
86 | semiGuardedBean.throwSilentException();
87 | }
88 |
89 | @Test
90 | public void testConvertSilentException() {
91 | final Optional optionalWithStringInside = semiGuardedBean.convertSilentException();
92 | Assert.assertNotNull(optionalWithStringInside);
93 | Assert.assertFalse(optionalWithStringInside.isPresent());
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/test/java/com/github/pscheidl/fortee/extension/FortExtensionTest.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.extension;
2 |
3 | import com.github.pscheidl.fortee.extension.beans.IncorrectFailsafeMethodContractBean;
4 | import com.github.pscheidl.fortee.extension.beans.IncorrectSemisafeMethodContractBean;
5 | import com.github.pscheidl.fortee.extension.beans.OneMethodWithIncorrectFailsafeContractBean;
6 | import com.github.pscheidl.fortee.extension.beans.OneMethodWithIncorrectSemisafeContractBean;
7 | import org.jboss.arquillian.container.spi.client.container.DeploymentException;
8 | import org.jboss.arquillian.container.test.api.Deployer;
9 | import org.jboss.arquillian.container.test.api.Deployment;
10 | import org.jboss.arquillian.junit.Arquillian;
11 | import org.jboss.arquillian.test.api.ArquillianResource;
12 | import org.jboss.shrinkwrap.api.ShrinkWrap;
13 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
14 | import org.jboss.shrinkwrap.api.spec.WebArchive;
15 | import org.jboss.shrinkwrap.resolver.api.maven.archive.importer.MavenImporter;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.rules.ExpectedException;
19 | import org.junit.runner.RunWith;
20 |
21 |
22 | @RunWith(Arquillian.class)
23 | public class FortExtensionTest {
24 | @Rule
25 | public ExpectedException expectedException = ExpectedException.none();
26 |
27 | @ArquillianResource
28 | private Deployer deployer;
29 |
30 | @Deployment(managed = false, name = "FAILING_GUARDED_METHOD")
31 | public static WebArchive createGuardedMethodDeployment() {
32 |
33 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
34 | .loadPomFromFile("pom.xml")
35 | .importBuildOutput()
36 | .as(JavaArchive.class);
37 |
38 | return ShrinkWrap.create(WebArchive.class)
39 | .addAsLibrary(as)
40 | .addClass(IncorrectFailsafeMethodContractBean.class)
41 | .addAsWebInfResource("beans.xml");
42 | }
43 |
44 | @Deployment(managed = false, name = "FAILING_GUARDED_BEAN")
45 | public static WebArchive createGuardedBeanDeployment() {
46 |
47 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
48 | .loadPomFromFile("pom.xml")
49 | .importBuildOutput()
50 | .as(JavaArchive.class);
51 |
52 | return ShrinkWrap.create(WebArchive.class)
53 | .addAsLibrary(as)
54 | .addClass(OneMethodWithIncorrectFailsafeContractBean.class)
55 | .addAsWebInfResource("beans.xml");
56 | }
57 |
58 | @Deployment(managed = false, name = "FAILING_SEMI_GUARDED_METHOD")
59 | public static WebArchive createSemiGuardedMethodDeployment() {
60 |
61 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
62 | .loadPomFromFile("pom.xml")
63 | .importBuildOutput()
64 | .as(JavaArchive.class);
65 |
66 | return ShrinkWrap.create(WebArchive.class)
67 | .addAsLibrary(as)
68 | .addClass(IncorrectSemisafeMethodContractBean.class)
69 | .addAsWebInfResource("beans.xml");
70 | }
71 |
72 | @Deployment(managed = false, name = "FAILING_SEMI_GUARDED_BEAN")
73 | public static WebArchive createSemiGuardedBeanDeployment() {
74 |
75 | final JavaArchive as = ShrinkWrap.create(MavenImporter.class)
76 | .loadPomFromFile("pom.xml")
77 | .importBuildOutput()
78 | .as(JavaArchive.class);
79 |
80 | return ShrinkWrap.create(WebArchive.class)
81 | .addAsLibrary(as)
82 | .addClass(OneMethodWithIncorrectSemisafeContractBean.class)
83 | .addAsWebInfResource("beans.xml");
84 | }
85 |
86 | @Test
87 | public void testSingleMethodGuarded() {
88 | expectedException.expect(DeploymentException.class);
89 | deployer.deploy("FAILING_GUARDED_METHOD");
90 | }
91 |
92 | @Test
93 | public void testGuardedBean() {
94 | expectedException.expect(DeploymentException.class);
95 | deployer.deploy("FAILING_GUARDED_BEAN");
96 | }
97 |
98 | @Test
99 | public void testSingleMethodSemoGuarded() {
100 | expectedException.expect(DeploymentException.class);
101 | deployer.deploy("FAILING_SEMI_GUARDED_METHOD");
102 | }
103 |
104 | @Test
105 | public void testSemiGuardedBean() {
106 | expectedException.expect(DeploymentException.class);
107 | deployer.deploy("FAILING_SEMI_GUARDED_BEAN");
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/com/github/pscheidl/fortee/extension/FortExtension.java:
--------------------------------------------------------------------------------
1 | package com.github.pscheidl.fortee.extension;
2 |
3 | import com.github.pscheidl.fortee.failsafe.Failsafe;
4 | import com.github.pscheidl.fortee.failsafe.FailsafeInterceptor;
5 | import com.github.pscheidl.fortee.failsafe.Semisafe;
6 |
7 | import javax.enterprise.event.Observes;
8 | import javax.enterprise.inject.spi.AnnotatedType;
9 | import javax.enterprise.inject.spi.Extension;
10 | import javax.enterprise.inject.spi.ProcessAnnotatedType;
11 | import java.util.Optional;
12 | import java.util.logging.Level;
13 | import java.util.logging.Logger;
14 |
15 | /**
16 | * Listens during CDI startup, registering necessary interceptors.
17 | *
18 | * @author Pavel Pscheidl
19 | */
20 | public class FortExtension implements Extension {
21 |
22 | private final Logger logger = Logger.getLogger(FortExtension.class.getName());
23 |
24 | /**
25 | * Inspects annotated types for usage of @Failsafe interceptor and checks the return types of intercepted methods.
26 | *
27 | * @param pat Annotated type to be processed
28 | * @param Generic type of AnnotatedType
29 | */
30 | public void inspectFailsafeAnnotated(@Observes ProcessAnnotatedType pat) {
31 | AnnotatedType annotatedType = pat.getAnnotatedType();
32 |
33 | if (FailsafeInterceptor.class.isAssignableFrom(annotatedType.getJavaClass())) {
34 | return;
35 | }
36 |
37 | if (annotatedType.isAnnotationPresent(Failsafe.class) || annotatedType.isAnnotationPresent(Semisafe.class)) {
38 | scanAllMethodsForIncorrectReturnType(annotatedType);
39 | scanAllMethodsForDeclaredThrowables(annotatedType);
40 | } else {
41 | findGuardedMethodsWithBadReturnType(annotatedType);
42 | findGuardedMethodsDeclaringExceptions(annotatedType);
43 | }
44 | }
45 |
46 | /**
47 | * Searches all methods declared in the underlying class for not having Optional return type.
48 | *
49 | * @param annotatedType Class annotated with @Failsafe annotation
50 | * @param Generic type of AnnotatedType
51 | * @return Potentially empty list of public methods not returning Optional.
52 | */
53 | private void scanAllMethodsForIncorrectReturnType(AnnotatedType annotatedType) {
54 | final long count = annotatedType.getMethods()
55 | .stream()
56 | .filter(annotatedMethod -> !annotatedMethod.getJavaMember().getReturnType().equals(Optional.class))
57 | .map(badMethod -> {
58 | final String error = String.format("A guarded method %s does not return Optional.",
59 | badMethod.getJavaMember().toString());
60 | logger.log(Level.INFO, error);
61 | return badMethod;
62 | })
63 | .count();
64 |
65 | if (count > 0) {
66 | throw new IncorrectMethodSignatureException("Found methods that violate Optional return contract.");
67 | }
68 | }
69 |
70 | /**
71 | * Searches all methods declared in the underlying class for throwables declared
72 | * @param annotatedType Class annotated with @Failsafe annotation
73 | * @param Generic type of AnnotatedType
74 | */
75 | private void scanAllMethodsForDeclaredThrowables(AnnotatedType annotatedType) {
76 | final long count = annotatedType.getMethods()
77 | .stream()
78 | .filter(annotatedMethod -> annotatedType.isAnnotationPresent(Failsafe.class) &&
79 | annotatedMethod.getJavaMember().getExceptionTypes().length != 0)
80 | .map(badMethod -> {
81 | final String error = String.format("A guarded method %s has declared exceptions thrown." +
82 | " Please remove the exception declaration from method's signature.",
83 | badMethod.getJavaMember().toString());
84 | logger.log(Level.INFO, error);
85 | return badMethod;
86 | })
87 | .count();
88 |
89 | if (count > 0) {
90 | throw new IncorrectMethodSignatureException("Found guarded methods with declared exceptions thrown." +
91 | " Please remove the exception declaration from their signatures signature.");
92 | }
93 | }
94 |
95 | /**
96 | * Searches methods in the underlying class annotated with @Failsafe annotation for not returning Optional.
97 | *
98 | * @param annotatedType Class annotated with @Failsafe annotation
99 | * @param Generic type of AnnotatedType
100 | * @return Potentially empty list of public methods not returning Optional.
101 | */
102 | private void findGuardedMethodsWithBadReturnType(AnnotatedType annotatedType) {
103 | final long count = annotatedType.getMethods()
104 | .stream()
105 | .filter(method -> (method.isAnnotationPresent(Failsafe.class) || method.isAnnotationPresent(Semisafe.class))
106 | && !method.getJavaMember().getReturnType().equals(Optional.class))
107 | .map(badMethod -> {
108 | final String error = String.format("A guarded method %s does not return Optional.",
109 | badMethod.getJavaMember().toString());
110 | logger.log(Level.INFO, error);
111 | return badMethod;
112 | })
113 | .count();
114 |
115 | if (count > 0) {
116 | throw new IncorrectMethodSignatureException("Found methods that violate Optional return contract.");
117 | }
118 | }
119 |
120 | /**
121 | * Searches methods in the underlying class annotated with @Failsafe annotation for throwables declared
122 | * @param annotatedType Class annotated with @Failsafe annotation
123 | * @param Generic type of AnnotatedType
124 | */
125 | private void findGuardedMethodsDeclaringExceptions(AnnotatedType annotatedType) {
126 | final long count = annotatedType.getMethods()
127 | .stream()
128 | .filter(method -> (method.isAnnotationPresent(Failsafe.class))
129 | && method.getJavaMember().getExceptionTypes().length != 0)
130 | .map(badMethod -> {
131 | final String error = String.format("A guarded method %s has declared exceptions thrown." +
132 | " Please remove the exception declaration from method's signature.",
133 | badMethod.getJavaMember().toString());
134 | logger.log(Level.INFO, error);
135 | return badMethod;
136 | })
137 | .count();
138 |
139 | if (count > 0) {
140 | throw new IncorrectMethodSignatureException("Found guarded methods with declared exceptions thrown." +
141 | " Please remove the exception declaration from their signatures signature.");
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.github.pscheidl
5 | fortee
6 | 1.2.0
7 |
8 |
9 | UTF-8
10 | 1.8
11 | 1.8
12 | 1.4.1.Final
13 | 20.0.0.Final
14 |
15 |
16 | FortEE
17 | Fault tolerance library for Java EE
18 | https://github.com/Pscheidl/FortEE
19 |
20 |
21 | MIT License
22 | https://opensource.org/licenses/MIT
23 |
24 |
25 |
26 |
27 | Pavel Pscheidl
28 | pavelpscheidl@gmail.com
29 | Pavel Pscheidl
30 | www.pavel.cool
31 |
32 |
33 |
34 | scm:git:git://github.com/Pscheidl/FortEE.git
35 | scm:git:ssh://github.com/Pscheidl/FortEE.git
36 | https://github.com/Pscheidl/FortEE
37 |
38 |
39 |
40 |
41 | test-wildfly-managed
42 |
43 | true
44 |
45 |
46 |
47 | org.wildfly.arquillian
48 | wildfly-arquillian-container-managed
49 | 2.1.1.Final
50 | test
51 |
52 |
53 | org.jboss.shrinkwrap.resolver
54 | shrinkwrap-resolver-depchain
55 | 2.2.0
56 | test
57 | pom
58 |
59 |
60 |
61 |
62 |
63 | src/test/resources
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-dependency-plugin
70 |
71 |
72 | unpack
73 | process-test-classes
74 |
75 | unpack
76 |
77 |
78 |
79 |
80 | org.wildfly
81 | wildfly-dist
82 | ${version.org.wildfly}
83 | zip
84 | false
85 | ${project.build.directory}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | release
98 |
99 |
100 | ossrh
101 | https://oss.sonatype.org/content/repositories/snapshots
102 |
103 |
104 | ossrh
105 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
106 |
107 |
108 |
109 |
110 |
111 |
112 | org.sonatype.plugins
113 | nexus-staging-maven-plugin
114 | 1.6.8
115 | true
116 |
117 | ossrh
118 | https://oss.sonatype.org/
119 | true
120 |
121 |
122 |
123 | org.apache.maven.plugins
124 | maven-source-plugin
125 | 3.0.1
126 |
127 |
128 | attach-sources
129 |
130 | jar-no-fork
131 |
132 |
133 |
134 |
135 |
136 | org.apache.maven.plugins
137 | maven-javadoc-plugin
138 | 2.10.4
139 |
140 |
141 | attach-javadocs
142 |
143 | jar
144 |
145 |
146 |
147 |
148 |
149 | org.apache.maven.plugins
150 | maven-gpg-plugin
151 | 1.6
152 |
153 |
154 | sign-artifacts
155 | verify
156 |
157 | sign
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | javax
170 | javaee-api
171 | 7.0
172 | provided
173 |
174 |
175 | junit
176 | junit
177 | 4.13.1
178 | test
179 |
180 |
181 | org.jboss.arquillian.junit
182 | arquillian-junit-container
183 | ${version.org.jboss.arquillian}
184 | test
185 |
186 |
187 | org.apache.commons
188 | commons-lang3
189 | 3.18.0
190 | test
191 |
192 |
193 |
194 |
195 |
196 |
197 | org.jboss.arquillian
198 | arquillian-bom
199 | ${version.org.jboss.arquillian}
200 | import
201 | pom
202 |
203 |
204 |
205 |
206 |
207 |
--------------------------------------------------------------------------------