├── .travis.yml ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ ├── services │ │ │ └── javax.enterprise.inject.spi.Extension │ │ │ └── beans.xml │ └── java │ │ └── com │ │ └── github │ │ └── pscheidl │ │ └── fortee │ │ ├── extension │ │ ├── IncorrectMethodSignatureException.java │ │ └── FortExtension.java │ │ └── failsafe │ │ ├── Failsafe.java │ │ ├── FailsafeInterceptor.java │ │ ├── Semisafe.java │ │ ├── DeclaredExceptionFailsafeInterceptor.java │ │ ├── ExecutionErrorEvent.java │ │ ├── NoExceptionsFailsafeInterceptor.java │ │ └── SemisafeInterceptor.java └── test │ ├── resources │ ├── beans.xml │ └── arquillian.xml │ └── java │ └── com │ └── github │ └── pscheidl │ └── fortee │ ├── extension │ ├── beans │ │ ├── IncorrectFailsafeMethodContractBean.java │ │ ├── IncorrectSemisafeMethodContractBean.java │ │ ├── OneMethodWithIncorrectFailsafeContractBean.java │ │ └── OneMethodWithIncorrectSemisafeContractBean.java │ └── FortExtensionTest.java │ └── failsafe │ ├── beans │ ├── NotFailingBean.java │ ├── FailingBean.java │ └── SemiGuardedBean.java │ ├── FailsafeInterceptorTest.java │ └── SemisafeInterceptorTest.java ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── README.md └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension: -------------------------------------------------------------------------------- 1 | com.github.pscheidl.fortee.extension.FortExtension -------------------------------------------------------------------------------- /src/test/resources/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | 10 | # Gradle folders 11 | build/ 12 | .gradle/ 13 | out/ 14 | 15 | # IntelliJ Idea folder 16 | .idea/ 17 | *.iml 18 | 19 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 20 | hs_err_pid* 21 | 22 | # generated WSDL files 23 | src/generated 24 | 25 | /target/ -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/extension/beans/IncorrectFailsafeMethodContractBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.extension.beans; 2 | 3 | import com.github.pscheidl.fortee.failsafe.Failsafe; 4 | 5 | import javax.enterprise.context.Dependent; 6 | 7 | @Dependent 8 | public class IncorrectFailsafeMethodContractBean { 9 | 10 | @Failsafe 11 | public String incorrectReturnTypeMethod() { 12 | return null; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/extension/beans/IncorrectSemisafeMethodContractBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.extension.beans; 2 | 3 | import com.github.pscheidl.fortee.failsafe.Semisafe; 4 | 5 | import javax.enterprise.context.Dependent; 6 | 7 | @Dependent 8 | public class IncorrectSemisafeMethodContractBean { 9 | 10 | @Semisafe({}) 11 | public String incorrectReturnTypeMethod() { 12 | return null; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/failsafe/beans/NotFailingBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.failsafe.beans; 2 | 3 | import javax.enterprise.context.Dependent; 4 | import java.util.Optional; 5 | 6 | /** 7 | * @author Pavel Pscheidl 8 | */ 9 | @Dependent 10 | public class NotFailingBean { 11 | 12 | public Optional returnOptionalWithStringInside() { 13 | return Optional.of("Value returned normally"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/extension/beans/OneMethodWithIncorrectFailsafeContractBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.extension.beans; 2 | 3 | import com.github.pscheidl.fortee.failsafe.Failsafe; 4 | 5 | import javax.enterprise.context.Dependent; 6 | import java.util.Optional; 7 | 8 | @Dependent 9 | @Failsafe 10 | public class OneMethodWithIncorrectFailsafeContractBean { 11 | 12 | public Optional correctMethodSignature() { 13 | return Optional.empty(); 14 | } 15 | 16 | public String incorrectMethodSignature() { 17 | return null; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/extension/beans/OneMethodWithIncorrectSemisafeContractBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.extension.beans; 2 | 3 | import com.github.pscheidl.fortee.failsafe.Semisafe; 4 | 5 | import javax.enterprise.context.Dependent; 6 | import java.util.Optional; 7 | 8 | @Dependent 9 | @Semisafe({}) 10 | public class OneMethodWithIncorrectSemisafeContractBean { 11 | 12 | public Optional correctMethodSignature() { 13 | return Optional.empty(); 14 | } 15 | 16 | public String incorrectMethodSignature() { 17 | return null; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | ${jbossHome:target/wildfly-20.0.0.Final} 9 | true 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/java/com/github/pscheidl/fortee/failsafe/beans/FailingBean.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.failsafe.beans; 2 | 3 | import com.github.pscheidl.fortee.failsafe.Failsafe; 4 | 5 | import javax.enterprise.context.Dependent; 6 | import java.util.Optional; 7 | 8 | /** 9 | * @author Pavel Pscheidl 10 | */ 11 | @Dependent 12 | public class FailingBean { 13 | 14 | @Failsafe 15 | public Optional throwError() { 16 | throw new IllegalArgumentException("Thrown on purpose"); 17 | } 18 | 19 | @Failsafe 20 | public Optional returnNull() { 21 | return null; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/pscheidl/fortee/extension/IncorrectMethodSignatureException.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.extension; 2 | 3 | /** 4 | * An exception thrown when a method signature does not meet criteria required. 5 | * 6 | * @author Pavel Pscheidl 7 | */ 8 | public class IncorrectMethodSignatureException extends RuntimeException { 9 | 10 | /** 11 | * Constructs a new {@link IncorrectMethodSignatureException} with cause left uninitialized. 12 | * 13 | * @param message Message with exception cause 14 | */ 15 | protected IncorrectMethodSignatureException(String message) { 16 | super(message); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/pscheidl/fortee/failsafe/Failsafe.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.failsafe; 2 | 3 | import javax.interceptor.InterceptorBinding; 4 | import java.lang.annotation.Inherited; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | 12 | /** 13 | * Binds {@link NoExceptionsFailsafeInterceptor} to a specific method or to each and every public-declared method when 14 | * placed on top of a CDI bean. 15 | * 16 | * @author Pavel Pscheidl 17 | */ 18 | @Inherited 19 | @InterceptorBinding 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target({METHOD, TYPE}) 22 | public @interface Failsafe { 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/pscheidl/fortee/failsafe/FailsafeInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.pscheidl.fortee.failsafe; 2 | 3 | import javax.enterprise.event.Event; 4 | import javax.inject.Inject; 5 | import javax.interceptor.InvocationContext; 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Abstract Failsafe interceptor converting unexpected erroneous states into an empty {@link java.util.Optional} 10 | */ 11 | public abstract class FailsafeInterceptor { 12 | 13 | @Inject 14 | private Event 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[] 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 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 | [![Build Status](https://travis-ci.org/Pscheidl/FortEE.svg?branch=master)](https://travis-ci.org/Pscheidl/FortEE) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/cab8a9609a9a4362a18c1ff3f759cf02)](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 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 | --------------------------------------------------------------------------------