Merge branch 'master' of github.com:apache/tomee
diff --git a/container/openejb-core/pom.xml b/container/openejb-core/pom.xml
index 2000d30..d28bcad 100644
--- a/container/openejb-core/pom.xml
+++ b/container/openejb-core/pom.xml
@@ -313,10 +313,10 @@
           </execution>
         </executions>
       </plugin>
+
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>${surefire.version}</version>
         <configuration>
           <childDelegation>true</childDelegation>
           <reuseForks>false</reuseForks>
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java b/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java
index 4207e74..a915654 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java
@@ -940,6 +940,11 @@
         }
     }
 
+    public void enrich(final Object inputTestInstance) throws org.apache.openejb.OpenEJBException {
+        final BeanContext context = SystemInstance.get().getComponent(ContainerSystem.class).getBeanContext(inputTestInstance.getClass());
+        enrich(inputTestInstance, context);
+    }
+
     private void enrich(final Object inputTestInstance, final BeanContext context) throws org.apache.openejb.OpenEJBException {
         if (context == null) {
             return;
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerBase.java b/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerBase.java
new file mode 100644
index 0000000..c54577d
--- /dev/null
+++ b/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerBase.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.testing;
+
+import org.apache.openejb.core.ThreadContext;
+import org.apache.openejb.util.JavaSecurityManagers;
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.inject.OWBInjector;
+import org.apache.xbean.finder.AnnotationFinder;
+import org.apache.xbean.finder.archive.FileArchive;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.apache.openejb.loader.JarLocation.jarLocation;
+
+// goal is to share the same container for all embedded tests and hold the config there
+// only works if all tests use the same config
+public class SingleApplicationComposerBase {
+    private static volatile boolean started = false;
+    private static final AtomicReference<Object> APP = new AtomicReference<>();
+    private static final AtomicReference<Thread> HOOK = new AtomicReference<>();
+
+    public void setApp(final Object o) {
+        APP.set(o);
+    }
+
+    public void close() {
+        final Thread hook = HOOK.get();
+        if (hook != null) {
+            hook.run();
+            Runtime.getRuntime().removeShutdownHook(hook);
+            HOOK.compareAndSet(hook, null);
+            APP.set(null);
+        }
+    }
+
+    public void start(final Class<?> marker) throws Exception {
+        if (APP.get() == null) {
+            final Class<?> type;
+            final String typeStr = JavaSecurityManagers.getSystemProperty("tomee.application-composer.application");
+            if (typeStr != null) {
+                try {
+                    type = Thread.currentThread().getContextClassLoader().loadClass(typeStr);
+                } catch (final ClassNotFoundException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            } else if (marker == null) {
+                throw new IllegalArgumentException("set tomee.application-composer.application system property or add a marker to the rule or runner");
+            } else {
+                final Iterator<Class<?>> descriptors =
+                    new AnnotationFinder(new FileArchive(Thread.currentThread().getContextClassLoader(), jarLocation(marker)), false)
+                        .findAnnotatedClasses(Application.class).iterator();
+                if (!descriptors.hasNext()) {
+                    throw new IllegalArgumentException("No descriptor class using @Application");
+                }
+                type = descriptors.next();
+                if (descriptors.hasNext()) {
+                    throw new IllegalArgumentException("Ambiguous @Application: " + type + ", " + descriptors.next());
+                }
+            }
+            try {
+                APP.compareAndSet(null, type.newInstance());
+            } catch (final InstantiationException | IllegalAccessException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+        if (!started) {
+            final Object app = APP.get();
+            final ApplicationComposers composers = new ApplicationComposers(app.getClass()) {
+                @Override
+                public void deployApp(final Object inputTestInstance) throws Exception {
+                    super.deployApp(inputTestInstance);
+                    if (!started) {
+                        final ThreadContext previous = ThreadContext.getThreadContext(); // done here for logging
+                        final ApplicationComposers comp = this;
+                        final Thread hook = new Thread() {
+                            @Override
+                            public void run() {
+                                try {
+                                    comp.after();
+                                } catch (final Exception e) {
+                                    ThreadContext.exit(previous);
+                                    throw new IllegalStateException(e);
+                                }
+                            }
+                        };
+                        HOOK.set(hook);
+                        Runtime.getRuntime().addShutdownHook(hook);
+                        started = true;
+                    }
+                }
+            };
+            composers.before(app);
+            composers.handleLifecycle(app.getClass(), app);
+        }
+    }
+
+    public void composerInject(final Object target) throws IllegalAccessException {
+        WebBeansContext wbc = null;
+        try {
+            wbc = WebBeansContext.currentInstance();
+        } catch (final IllegalStateException ise) {
+            // no-op
+        }
+        if (wbc != null) {
+            OWBInjector.inject(wbc.getBeanManagerImpl(), target, null);
+        }
+
+        final Object app = APP.get();
+        final Class<?> aClass = target.getClass();
+        for (final Field f : aClass.getDeclaredFields()) {
+            if (f.isAnnotationPresent(RandomPort.class)) {
+                for (final Field field : app.getClass().getDeclaredFields()) {
+                    if (field.getType() ==  f.getType()) {
+                        if (!field.isAccessible()) {
+                            field.setAccessible(true);
+                        }
+                        if (!f.isAccessible()) {
+                            f.setAccessible(true);
+                        }
+
+                        final Object value = field.get(app);
+                        f.set(target, value);
+                        break;
+                    }
+                }
+            } else if (f.isAnnotationPresent(Application.class)) {
+                if (!f.isAccessible()) {
+                    f.setAccessible(true);
+                }
+                f.set(target, app);
+            }
+        }
+        final Class<?> superclass = aClass.getSuperclass();
+        if (superclass != Object.class) {
+            composerInject(superclass);
+        }
+    }
+
+    public boolean isStarted() {
+        return started;
+    }
+}
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerRunner.java b/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerRunner.java
index b35b7f5..1b1eb07 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerRunner.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/testing/SingleApplicationComposerRunner.java
@@ -16,12 +16,6 @@
  */
 package org.apache.openejb.testing;
 
-import org.apache.openejb.core.ThreadContext;
-import org.apache.openejb.util.JavaSecurityManagers;
-import org.apache.webbeans.config.WebBeansContext;
-import org.apache.webbeans.inject.OWBInjector;
-import org.apache.xbean.finder.AnnotationFinder;
-import org.apache.xbean.finder.archive.FileArchive;
 import org.junit.rules.MethodRule;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -31,19 +25,12 @@
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 
-import java.lang.reflect.Field;
-import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static org.apache.openejb.loader.JarLocation.jarLocation;
 
 // goal is to share the same container for all embedded tests and hold the config there
 // only works if all tests use the same config
 public class SingleApplicationComposerRunner extends BlockJUnit4ClassRunner {
-    private static volatile boolean started = false;
-    private static final AtomicReference<Object> APP = new AtomicReference<>();
-    private static final AtomicReference<Thread> HOOK = new AtomicReference<>();
+    private static final SingleApplicationComposerBase BASE = new SingleApplicationComposerBase();
 
     // use when you use another runner like Parameterized of JUnit
     public static class Rule implements TestRule {
@@ -58,8 +45,8 @@
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
-                    start(test.getClass());
-                    composerInject(test);
+                    BASE.start(test.getClass());
+                    BASE.composerInject(test);
                     base.evaluate();
                 }
             };
@@ -69,22 +56,12 @@
     public static class Start extends RunListener {
         @Override
         public void testStarted(final Description description) throws Exception {
-            start(null);
+            BASE.start(null);
         }
     }
 
     public static void setApp(final Object o) {
-        APP.set(o);
-    }
-
-    public static void close() {
-        final Thread hook = HOOK.get();
-        if (hook != null) {
-            hook.run();
-            Runtime.getRuntime().removeShutdownHook(hook);
-            HOOK.compareAndSet(hook, null);
-            APP.set(null);
-        }
+        BASE.setApp(o);
     }
 
     public SingleApplicationComposerRunner(final Class<?> klass) throws InitializationError {
@@ -100,8 +77,8 @@
                 return new Statement() {
                     @Override
                     public void evaluate() throws Throwable {
-                        start(getTestClass().getJavaClass());
-                        composerInject(target);
+                        BASE.start(getTestClass().getJavaClass());
+                        BASE.composerInject(target);
                         base.evaluate();
                     }
                 };
@@ -109,107 +86,4 @@
         });
         return rules;
     }
-
-    private static void start(final Class<?> marker) throws Exception {
-        if (APP.get() == null) {
-            final Class<?> type;
-            final String typeStr = JavaSecurityManagers.getSystemProperty("tomee.application-composer.application");
-            if (typeStr != null) {
-                try {
-                    type = Thread.currentThread().getContextClassLoader().loadClass(typeStr);
-                } catch (final ClassNotFoundException e) {
-                    throw new IllegalArgumentException(e);
-                }
-            } else if (marker == null) {
-                throw new IllegalArgumentException("set tomee.application-composer.application system property or add a marker to the rule or runner");
-            } else {
-                final Iterator<Class<?>> descriptors =
-                    new AnnotationFinder(new FileArchive(Thread.currentThread().getContextClassLoader(), jarLocation(marker)), false)
-                        .findAnnotatedClasses(Application.class).iterator();
-                if (!descriptors.hasNext()) {
-                    throw new IllegalArgumentException("No descriptor class using @Application");
-                }
-                type = descriptors.next();
-                if (descriptors.hasNext()) {
-                    throw new IllegalArgumentException("Ambiguous @Application: " + type + ", " + descriptors.next());
-                }
-            }
-            try {
-                APP.compareAndSet(null, type.newInstance());
-            } catch (final InstantiationException | IllegalAccessException e) {
-                throw new IllegalStateException(e);
-            }
-        }
-        if (!started) {
-            final Object app = APP.get();
-            final ApplicationComposers composers = new ApplicationComposers(app.getClass()) {
-                @Override
-                public void deployApp(final Object inputTestInstance) throws Exception {
-                    super.deployApp(inputTestInstance);
-                    if (!started) {
-                        final ThreadContext previous = ThreadContext.getThreadContext(); // done here for logging
-                        final ApplicationComposers comp = this;
-                        final Thread hook = new Thread() {
-                            @Override
-                            public void run() {
-                                try {
-                                    comp.after();
-                                } catch (final Exception e) {
-                                    ThreadContext.exit(previous);
-                                    throw new IllegalStateException(e);
-                                }
-                            }
-                        };
-                        HOOK.set(hook);
-                        Runtime.getRuntime().addShutdownHook(hook);
-                        started = true;
-                    }
-                }
-            };
-            composers.before(app);
-            composers.handleLifecycle(app.getClass(), app);
-        }
-    }
-
-    private static void composerInject(final Object target) throws IllegalAccessException {
-        WebBeansContext wbc = null;
-        try {
-            wbc = WebBeansContext.currentInstance();
-        } catch (final IllegalStateException ise) {
-            // no-op
-        }
-        if (wbc != null) {
-            OWBInjector.inject(wbc.getBeanManagerImpl(), target, null);
-        }
-
-        final Object app = APP.get();
-        final Class<?> aClass = target.getClass();
-        for (final Field f : aClass.getDeclaredFields()) {
-            if (f.isAnnotationPresent(RandomPort.class)) {
-                for (final Field field : app.getClass().getDeclaredFields()) {
-                    if (field.getType() ==  f.getType()) {
-                        if (!field.isAccessible()) {
-                            field.setAccessible(true);
-                        }
-                        if (!f.isAccessible()) {
-                            f.setAccessible(true);
-                        }
-
-                        final Object value = field.get(app);
-                        f.set(target, value);
-                        break;
-                    }
-                }
-            } else if (f.isAnnotationPresent(Application.class)) {
-                if (!f.isAccessible()) {
-                    f.setAccessible(true);
-                }
-                f.set(target, app);
-            }
-        }
-        final Class<?> superclass = aClass.getSuperclass();
-        if (superclass != Object.class) {
-            composerInject(superclass);
-        }
-    }
 }
diff --git a/container/openejb-junit5-backward/pom.xml b/container/openejb-junit5-backward/pom.xml
index bdd326a..247cdd5 100644
--- a/container/openejb-junit5-backward/pom.xml
+++ b/container/openejb-junit5-backward/pom.xml
@@ -34,13 +34,11 @@
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
-            <version>${junit.jupiter.version}</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-engine</artifactId>
-            <version>${junit.jupiter.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -62,7 +60,7 @@
         </dependency>
     </dependencies>
     <properties>
-        <tomee.build.name>${project.groupId}.container.junit5</tomee.build.name>
+        <tomee.build.name>${project.groupId}.container.junit5.backward</tomee.build.name>
         <netbeans.hint.license>openejb</netbeans.hint.license>
     </properties>
 
@@ -71,7 +69,6 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>${surefire.junit5.version}</version>
             </plugin>
         </plugins>
     </build>
diff --git a/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/EjbContainerExtension.java b/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/EjbContainerExtension.java
index 13bd310..f99d4ec 100644
--- a/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/EjbContainerExtension.java
+++ b/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/EjbContainerExtension.java
@@ -18,6 +18,7 @@
 
 import org.apache.openejb.Injector;
 import org.apache.openejb.OpenEJBException;
+import org.apache.openejb.OpenEJBRuntimeException;
 import org.apache.openejb.OpenEjbContainer;
 import org.apache.openejb.injection.FallbackPropertyInjector;
 import org.apache.openejb.junit.jee.config.Properties;
@@ -48,12 +49,8 @@
     @Override
     public void beforeAll(ExtensionContext extensionContext) throws Exception {
 
-        Optional<Class<?>> oClazz = extensionContext.getTestClass();
-
-        if (!oClazz.isPresent()) {
-            throw new RuntimeException("Could not get class from extension context");
-        }
-        Class<?> clazz = oClazz.get();
+        Class<?> clazz = extensionContext.getTestClass()
+                .orElseThrow(() -> new OpenEJBRuntimeException("Could not get class from extension context"));
 
         properties = new java.util.Properties();
 
@@ -119,8 +116,8 @@
 
     @Override
     public void beforeEach(ExtensionContext extensionContext) throws Exception {
-        Class<?> clazz = extensionContext.getTestClass().isPresent()  ? extensionContext.getTestClass().get() : null;
-        Object test =  extensionContext.getTestInstance().isPresent() ? extensionContext.getTestInstance().get() : null;
+        Class<?> clazz = extensionContext.getTestClass().orElse(null);
+        Object test =  extensionContext.getTestInstance().orElse(null);
 
         if (clazz != null){
 
diff --git a/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/transaction/TransactionExtension.java b/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/transaction/TransactionExtension.java
index 369f176..fe52498 100644
--- a/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/transaction/TransactionExtension.java
+++ b/container/openejb-junit5-backward/src/main/java/org/apache/openejb/junit5/jee/transaction/TransactionExtension.java
@@ -16,6 +16,7 @@
  */
 package org.apache.openejb.junit5.jee.transaction;
 
+import org.apache.openejb.OpenEJBRuntimeException;
 import org.apache.openejb.core.transaction.JtaTransactionPolicyFactory;
 import org.apache.openejb.core.transaction.TransactionPolicy;
 import org.apache.openejb.core.transaction.TransactionType;
@@ -30,7 +31,8 @@
 
     @Override
     public void afterTestExecution(ExtensionContext extensionContext) throws Exception {
-        final Method mtd = extensionContext.getTestMethod().get();
+        final Method mtd = extensionContext.getTestMethod()
+                .orElseThrow(() -> new OpenEJBRuntimeException("Could not get test method from extension context."));
         final Transaction tx = mtd.getAnnotation(Transaction.class);
         if (tx != null) {
             final TransactionManager transactionManager = SystemInstance.get().getComponent(TransactionManager.class);
diff --git a/container/openejb-junit5/LICENSE b/container/openejb-junit5/LICENSE
new file mode 100644
index 0000000..10d7a73
--- /dev/null
+++ b/container/openejb-junit5/LICENSE
@@ -0,0 +1,417 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+=================================================================================
+JUnit
+
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+      a) in the case of the initial Contributor, the initial code and
+         documentation distributed under this Agreement, and
+      b) in the case of each subsequent Contributor:
+
+      i) changes to the Program, and
+
+      ii) additions to the Program;
+
+      where such changes and/or additions to the Program originate from and are
+distributed by that particular Contributor. A Contribution 'originates' from a
+Contributor if it was added to the Program by such Contributor itself or anyone
+acting on such Contributor's behalf. Contributions do not include additions to
+the Program which: (i) are separate modules of software distributed in
+conjunction with the Program under their own license agreement, and (ii) are
+not derivative works of the Program. 
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+      a) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free copyright license to
+reproduce, prepare derivative works of, publicly display, publicly perform,
+distribute and sublicense the Contribution of such Contributor, if any, and
+such derivative works, in source code and object code form.
+
+      b) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free patent license under
+Licensed Patents to make, use, sell, offer to sell, import and otherwise
+transfer the Contribution of such Contributor, if any, in source code and
+object code form. This patent license shall apply to the combination of the
+Contribution and the Program if, at the time the Contribution is added by the
+Contributor, such addition of the Contribution causes such combination to be
+covered by the Licensed Patents. The patent license shall not apply to any
+other combinations which include the Contribution. No hardware per se is
+licensed hereunder. 
+
+      c) Recipient understands that although each Contributor grants the
+licenses to its Contributions set forth herein, no assurances are provided by
+any Contributor that the Program does not infringe the patent or other
+intellectual property rights of any other entity. Each Contributor disclaims
+any liability to Recipient for claims brought by any other entity based on
+infringement of intellectual property rights or otherwise. As a condition to
+exercising the rights and licenses granted hereunder, each Recipient hereby
+assumes sole responsibility to secure any other intellectual property rights
+needed, if any. For example, if a third party patent license is required to
+allow Recipient to distribute the Program, it is Recipient's responsibility to
+acquire that license before distributing the Program.
+
+      d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright license
+set forth in this Agreement. 
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under
+its own license agreement, provided that:
+
+      a) it complies with the terms and conditions of this Agreement; and
+
+      b) its license agreement:
+
+      i) effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose; 
+
+      ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and consequential
+damages, such as lost profits; 
+
+      iii) states that any provisions which differ from this Agreement are
+offered by that Contributor alone and not by any other party; and
+
+      iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable manner on
+or through a medium customarily used for software exchange. 
+
+When the Program is made available in source code form:
+
+      a) it must be made available under this Agreement; and 
+
+      b) a copy of this Agreement must be included with each copy of the
+Program. 
+
+Contributors may not remove or alter any copyright notices contained within the
+Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if
+any, in a manner that reasonably allows subsequent Recipients to identify the
+originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore, if
+a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses, damages
+and costs (collectively "Losses") arising from claims, lawsuits and other legal
+actions brought by a third party against the Indemnified Contributor to the
+extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor
+to control, and cooperate with the Commercial Contributor in, the defense and
+any related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If that
+Commercial Contributor then makes performance claims, or offers warranties
+related to Product X, those performance claims and warranties are such
+Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a court
+requires any other Contributor to pay any damages as a result, the Commercial
+Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using
+and distributing the Program and assumes all risks associated with its exercise
+of rights under this Agreement, including but not limited to the risks and
+costs of program errors, compliance with applicable laws, damage to or loss of
+data, programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable
+law, it shall not affect the validity or enforceability of the remainder of the
+terms of this Agreement, and without further action by the parties hereto, such
+provision shall be reformed to the minimum extent necessary to make such
+provision valid and enforceable.
+
+If Recipient institutes patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's rights
+granted under Section 2(b) shall terminate as of the date such litigation is
+filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue
+and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to time.
+No one other than the Agreement Steward has the right to modify this Agreement.
+The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each new version
+of the Agreement will be given a distinguishing version number. The Program
+(including Contributions) may always be distributed subject to the version of
+the Agreement under which it was received. In addition, after a new version of
+the Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
+the intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial
+in any resulting litigation. 
+
diff --git a/container/openejb-junit5/NOTICE b/container/openejb-junit5/NOTICE
new file mode 100644
index 0000000..a9f2f42
--- /dev/null
+++ b/container/openejb-junit5/NOTICE
@@ -0,0 +1,9 @@
+
+Apache OpenEJB
+Copyright 1999-2014 The Apache OpenEJB development community
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+==========================================================
+junit - JUnit is a simple framework to write repeatable tests (http://junit.org/)
+License: Eclipse Public License - v 1.0
diff --git a/container/openejb-junit5/pom.xml b/container/openejb-junit5/pom.xml
new file mode 100644
index 0000000..589e573
--- /dev/null
+++ b/container/openejb-junit5/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>container</artifactId>
+        <groupId>org.apache.tomee</groupId>
+        <version>8.0.7-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>openejb-junit5</artifactId>
+    <packaging>jar</packaging>
+    <name>TomEE :: Container :: JUnit 5</name>
+
+    <properties>
+        <tomee.build.name>${project.groupId}.container.junit5</tomee.build.name>
+        <netbeans.hint.license>openejb</netbeans.hint.license>
+        <jacocoArgLine/>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>openejb-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.openejb.itests</groupId>
+            <artifactId>failover-ejb</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <childDelegation>true</childDelegation>
+                    <reuseForks>false</reuseForks>
+                    <forkCount>1</forkCount>
+                    <argLine>
+                        ${jacocoArgLine}
+                        -enableassertions
+                    </argLine>
+                    <workingDirectory>${project.basedir}/target</workingDirectory>
+                    <systemPropertyVariables>
+                        <openejb.home>${project.basedir}/target/test-classes</openejb.home>
+                    </systemPropertyVariables>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>default-test</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <excludes>
+                                <exclude>org/apache/openejb/junit5/SingleAppComposerTest</exclude>
+                                <exclude>org/apache/openejb/junit5/SingleAppComposerJVMTest</exclude>
+                            </excludes>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>per-jvm-tests</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <forkCount>0</forkCount>
+                            <includes>
+                                <include>org/apache/openejb/junit5/SingleAppComposerTest</include>
+                                <include>org/apache/openejb/junit5/SingleAppComposerJVMTest</include>
+                            </includes>
+                            <argLine>
+                                ${jacocoArgLine}
+                                -enableassertions
+                                -Djunit.jupiter.testclass.order.default=org.apache.openejb.junit5.order.AppComposerTestClassOrderer
+                                -Dtomee.application-composer.application=org.apache.openejb.junit5.app.MyApp
+                            </argLine>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterAllReleaser.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterAllReleaser.java
new file mode 100644
index 0000000..fcc65b9
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterAllReleaser.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class AfterAllReleaser extends AfterReleaserBase {
+
+    AfterAllReleaser(ExtensionContext.Namespace namespace) {
+        super(namespace);
+    }
+
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterEachReleaser.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterEachReleaser.java
new file mode 100644
index 0000000..162c768
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterEachReleaser.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class AfterEachReleaser extends AfterReleaserBase {
+
+    AfterEachReleaser(ExtensionContext.Namespace namespace) {
+        super(namespace);
+    }
+}
+
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterReleaserBase.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterReleaserBase.java
new file mode 100644
index 0000000..920c536
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/AfterReleaserBase.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.testing.ApplicationComposers;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public abstract class AfterReleaserBase extends ApplicationComposerExtensionBase {
+
+    private final ExtensionContext.Namespace namespace;
+
+    AfterReleaserBase(ExtensionContext.Namespace namespace) {
+        this.namespace = namespace;
+    }
+
+    void run(final ExtensionContext extensionContext) throws Exception {
+        extensionContext.getStore(namespace).get(ApplicationComposers.class, ApplicationComposers.class).after();
+    }
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtension.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtension.java
new file mode 100644
index 0000000..eaacb3d
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtension.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.OpenEJBRuntimeException;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ApplicationComposerExtension extends ApplicationComposerExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(ApplicationComposerExtension.class.getName());
+
+    private final Object[] modules;
+
+    public ApplicationComposerExtension() {
+        this((Object[]) null);
+    }
+
+    public ApplicationComposerExtension(Object... modules) {
+        this.modules = modules;
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext context) throws Exception {
+
+        if (isPerJvm(context)) {
+            if(this.modules != null && this.modules.length > 0) {
+                throw new IllegalArgumentException("Modules are not supported in PER_JVM mode. Set 'tomee.application-composer.application' system property.");
+            }
+            context.getStore(NAMESPACE).put(ApplicationComposerPerXYExtensionBase.class, new ApplicationComposerPerJVMExtension());
+        } else if (isPerAll(context)) {
+            context.getStore(NAMESPACE).put(ApplicationComposerPerXYExtensionBase.class, new ApplicationComposerPerAllExtension(this.modules));
+        } else if (isPerEach(context)) {
+            context.getStore(NAMESPACE).put(ApplicationComposerPerXYExtensionBase.class, new ApplicationComposerPerEachExtension(this.modules));
+        } else if (isPerDefault(context)) {
+            context.getStore(NAMESPACE).put(ApplicationComposerPerXYExtensionBase.class, new ApplicationComposerPerDefaultExtension(this.modules));
+        } else {
+            throw new OpenEJBRuntimeException("No ExtensionMode is present.");
+        }
+
+        context.getStore(NAMESPACE).get(ApplicationComposerPerXYExtensionBase.class, ApplicationComposerPerXYExtensionBase.class).beforeAll(context);
+    }
+
+    @Override
+    public void afterAll(ExtensionContext context) throws Exception {
+        ApplicationComposerPerXYExtensionBase delegate = context.getStore(NAMESPACE).get(ApplicationComposerPerXYExtensionBase.class, ApplicationComposerPerXYExtensionBase.class);
+        if(delegate != null) {
+            delegate.afterAll(context);
+        }
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) throws Exception {
+        ApplicationComposerPerXYExtensionBase delegate = context.getStore(NAMESPACE).get(ApplicationComposerPerXYExtensionBase.class, ApplicationComposerPerXYExtensionBase.class);
+        if(delegate != null) {
+            delegate.beforeEach(context);
+        }
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) throws Exception {
+        ApplicationComposerPerXYExtensionBase delegate = context.getStore(NAMESPACE).get(ApplicationComposerPerXYExtensionBase.class, ApplicationComposerPerXYExtensionBase.class);
+        if(delegate != null) {
+            delegate.afterEach(context);
+        }
+    }
+
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtensionBase.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtensionBase.java
new file mode 100644
index 0000000..24a095b
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerExtensionBase.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.util.AnnotationUtils;
+
+import java.util.Optional;
+
+public abstract class ApplicationComposerExtensionBase {
+
+    boolean isPerClassLifecycle(final ExtensionContext context) {
+        return isPerTestInstanceLifecycle(context, TestInstance.Lifecycle.PER_CLASS);
+    }
+
+    boolean isPerMethodLifecycle(final ExtensionContext context) {
+        return isPerTestInstanceLifecycle(context, TestInstance.Lifecycle.PER_METHOD);
+    }
+
+    boolean isPerTestInstanceLifecycle(final ExtensionContext context, TestInstance.Lifecycle lifecycle) {
+        return context.getTestInstanceLifecycle()
+                .map(it -> it.equals(lifecycle))
+                .orElse(false);
+    }
+
+    protected boolean isPerEach(final ExtensionContext context) {
+        return checkMode(context, ExtensionMode.PER_EACH);
+    }
+
+    boolean isPerAll(final ExtensionContext context) {
+        return checkMode(context, ExtensionMode.PER_ALL);
+    }
+
+    boolean isPerJvm(final ExtensionContext context) {
+        return checkMode(context, ExtensionMode.PER_JVM);
+    }
+
+    boolean isPerDefault(final ExtensionContext context) {
+        return checkMode(context, ExtensionMode.AUTO);
+    }
+
+    boolean checkMode(final ExtensionContext context, ExtensionMode extensionMode ) {
+       return extensionMode == getModeFromAnnotation(context);
+    }
+
+    ExtensionMode getModeFromAnnotation(final ExtensionContext context) {
+        return context.getTestClass()
+                .flatMap(test -> AnnotationUtils.findAnnotation(test, RunWithApplicationComposer.class))
+                .map(RunWithApplicationComposer::mode)
+                .orElse(ExtensionMode.AUTO);
+    }
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerAllExtension.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerAllExtension.java
new file mode 100644
index 0000000..7f901ae
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerAllExtension.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ApplicationComposerPerAllExtension extends ApplicationComposerPerXYExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, ApplicationComposerPerAllExtension.class);
+
+    public ApplicationComposerPerAllExtension() {
+        this((Object[]) null);
+    }
+
+    public ApplicationComposerPerAllExtension(Object... modules) {
+        super(modules);
+    }
+
+    @Override
+    protected void validate(ExtensionContext context) {
+        super.validate(context);
+        if (isPerAll(context) && isPerMethodLifecycle(context)) {
+            logger.info("Running PER_ALL in combination with TestInstance.Lifecycle.PER_METHOD.");
+            logger.info("Please note, there are some limitations (N = amount of test methods):");
+            logger.info("N = 1: Will work as expected.");
+            logger.info("N > 1: Injections are lost after the first test method was executed.");
+            logger.info("N > 1: Use CDI.current(), InitialContext or pure (http) client to implement the test.");
+        }
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext context) throws Exception {
+        super.beforeAll(context);
+        doInit(context);
+        doStart(context);
+        doInject(context);
+        addAfterAllReleaser(context);
+    }
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerDefaultExtension.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerDefaultExtension.java
new file mode 100644
index 0000000..1b88018
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerDefaultExtension.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ApplicationComposerPerDefaultExtension extends ApplicationComposerPerXYExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    public ApplicationComposerPerDefaultExtension() {
+        this((Object[]) null);
+    }
+
+    public ApplicationComposerPerDefaultExtension(Object... modules) {
+        super(modules);
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext context) throws Exception {
+        super.beforeAll(context);
+        if (isPerClassLifecycle(context)) {
+            doInit(context);
+            doStart(context);
+            doInject(context);
+            addAfterAllReleaser(context);
+        } else {
+            addAfterEachReleaser(context);
+        }
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) throws Exception {
+        if (isPerMethodLifecycle(context)) {
+            doInit(context);
+            doStart(context);
+            doInject(context);
+        }
+    }
+
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerEachExtension.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerEachExtension.java
new file mode 100644
index 0000000..5e1e3d1
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerEachExtension.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ApplicationComposerPerEachExtension extends ApplicationComposerPerXYExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    public ApplicationComposerPerEachExtension() {
+        this((Object[]) null);
+    }
+
+    public ApplicationComposerPerEachExtension(Object... modules) {
+        super(modules);
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) {
+        addAfterEachReleaser(context);
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) throws Exception {
+        doInit(context);
+        doStart(context);
+        doInject(context);
+    }
+
+}
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerJVMExtension.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerJVMExtension.java
new file mode 100644
index 0000000..7a9e974
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerJVMExtension.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.OpenEJBRuntimeException;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.Component;
+import org.apache.openejb.testing.Default;
+import org.apache.openejb.testing.Jars;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.SingleApplicationComposerBase;
+import org.apache.xbean.finder.ClassFinder;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.openejb.util.Classes.ancestors;
+
+public class ApplicationComposerPerJVMExtension extends ApplicationComposerPerXYExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    private static final SingleApplicationComposerBase BASE = new SingleApplicationComposerBase();
+
+    @Override
+    protected void validate(ExtensionContext context) {
+        if (!isPerJvm(context) && BASE.isStarted()) {
+            //XXX: Future work: We might get it to work via a JVM singleton/lock, see https://github.com/apache/tomee/pull/767#discussion_r595343572
+            throw new OpenEJBRuntimeException("Cannot run PER_JVM in combination with PER_ALL, PER_EACH or AUTO");
+        }
+
+        Class<?> clazz = context.getTestClass()
+                .orElseThrow(() -> new OpenEJBRuntimeException("Could not obtain test class from extension context"));
+
+        final List<Throwable> errors = new ArrayList<>();
+
+        ClassFinder classFinder = new ClassFinder(ancestors(clazz));
+
+        Class<? extends Annotation>[] toCheck = new Class[]{Component.class, Module.class, Classes.class, Default.class, Jars.class};
+
+        for (Class<? extends Annotation> annotation : toCheck) {
+            if (classFinder.isAnnotationPresent(annotation)) {
+                errors.add(new Exception("@" + annotation.getName() + " is not allowed with @Application in PER_JVM mode"));
+            }
+        }
+
+        if (!errors.isEmpty()) {
+            throw new OpenEJBRuntimeException(errors.toString());
+        }
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext context) throws Exception {
+        super.beforeAll(context);
+
+        doInit(context);
+        doStart(context);
+        if (isPerClassLifecycle(context)) {
+            doInject(context);
+        }
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) {
+        if (isPerMethodLifecycle(context)) {
+            doInject(context);
+        }
+    }
+
+    @Override
+    void doInit(final ExtensionContext extensionContext) {
+        //no-op
+    }
+
+    @Override
+    void doStart(final ExtensionContext extensionContext) throws Exception {
+        BASE.start(extensionContext.getTestClass().orElse(null));
+    }
+
+    @Override
+    void doInject(Object target, final ExtensionContext context) throws Exception {
+        BASE.composerInject(target);
+    }
+
+    public static boolean isStarted() {
+        return BASE.isStarted();
+    }
+
+}
+
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerXYExtensionBase.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerXYExtensionBase.java
new file mode 100644
index 0000000..7a19e70
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ApplicationComposerPerXYExtensionBase.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.OpenEJBRuntimeException;
+import org.apache.openejb.testing.ApplicationComposers;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestInstances;
+
+import java.util.List;
+
+abstract class ApplicationComposerPerXYExtensionBase extends ApplicationComposerExtensionBase implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(ApplicationComposerPerXYExtensionBase.class.getName());
+
+    private final Object[] modules;
+
+    public ApplicationComposerPerXYExtensionBase() {
+        this((Object[]) null);
+    }
+
+    public ApplicationComposerPerXYExtensionBase(Object... modules) {
+        this.modules = modules;
+    }
+
+    protected void validate(ExtensionContext context) {
+        if (!isPerJvm(context) && ApplicationComposerPerJVMExtension.isStarted()) {
+            //XXX: Future work: We might get it to work via a JVM singleton/lock, see https://github.com/apache/tomee/pull/767#discussion_r595343572
+            throw new OpenEJBRuntimeException("Cannot run PER_JVM in combination with PER_ALL, PER_EACH or AUTO");
+        }
+    }
+
+    @Override
+    public void afterAll(ExtensionContext context) throws Exception {
+        AfterAllReleaser releaser = context.getStore(NAMESPACE).get(AfterAllReleaser.class, AfterAllReleaser.class);
+        if (releaser != null) {
+            releaser.run(context);
+        }
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) throws Exception {
+        AfterEachReleaser releaser = context.getStore(NAMESPACE).get(AfterEachReleaser.class, AfterEachReleaser.class);
+        if (releaser != null) {
+            releaser.run(context);
+        }
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext extensionContext) throws Exception {
+        validate(extensionContext);
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext extensionContext) throws Exception {
+        //no-op
+    }
+
+    void doInit(final ExtensionContext extensionContext) {
+        Class<?> oClazz = extensionContext.getTestClass()
+                .orElseThrow(() -> new OpenEJBRuntimeException("Could not get test class from the given extension context."));
+
+        extensionContext.getStore(NAMESPACE).put(ApplicationComposers.class,
+                new ApplicationComposers(oClazz, this.modules));
+
+    }
+
+    void doStart(final ExtensionContext extensionContext) throws Exception {
+        TestInstances oTestInstances = extensionContext.getTestInstances()
+                .orElseThrow(() -> new OpenEJBRuntimeException("No test instances available for the given extension context."));
+
+        List<Object> testInstances = oTestInstances.getAllInstances();
+
+        ApplicationComposers delegate = extensionContext.getStore(NAMESPACE)
+                .get(ApplicationComposers.class, ApplicationComposers.class);
+
+        testInstances.forEach(t -> {
+            try {
+                delegate.before(t);
+            } catch (Exception e) {
+                throw new OpenEJBRuntimeException(e);
+            }
+        });
+    }
+
+    void doInject(final ExtensionContext extensionContext) {
+        TestInstances oTestInstances = extensionContext.getTestInstances()
+                .orElseThrow(() -> new OpenEJBRuntimeException("No test instances available for the given extension context."));
+
+        List<Object> testInstances = oTestInstances.getAllInstances();
+
+        testInstances.forEach(target -> {
+            try {
+               doInject(target, extensionContext);
+            } catch (Exception e) {
+                throw new OpenEJBRuntimeException(e);
+            }
+        });
+    }
+
+    void doInject(Object target, final ExtensionContext context) throws Exception {
+        ApplicationComposers delegate = context.getStore(NAMESPACE)
+                .get(ApplicationComposers.class, ApplicationComposers.class);
+        delegate.enrich(target);
+    }
+
+    void addAfterAllReleaser(ExtensionContext context) {
+        context.getStore(NAMESPACE).put(AfterAllReleaser.class, new AfterAllReleaser(NAMESPACE));
+    }
+
+    void addAfterEachReleaser(ExtensionContext context) {
+        context.getStore(NAMESPACE).put(AfterEachReleaser.class, new AfterEachReleaser(NAMESPACE));
+    }
+}
+
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ExtensionMode.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ExtensionMode.java
new file mode 100644
index 0000000..4eb8da1
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/ExtensionMode.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+public enum ExtensionMode {
+
+    /**
+     * When using this mode, a container instance will be created once for the JVM lifetime.
+     */
+    PER_JVM,
+
+    /**
+     * When using this mode, a container instance will be created for each test class
+     */
+    PER_ALL,
+
+    /**
+     * When using this mode, a container instance will be created for each test
+     */
+    PER_EACH,
+
+    AUTO;
+
+}
\ No newline at end of file
diff --git a/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/RunWithApplicationComposer.java b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/RunWithApplicationComposer.java
new file mode 100644
index 0000000..12ee2b3
--- /dev/null
+++ b/container/openejb-junit5/src/main/java/org/apache/openejb/junit5/RunWithApplicationComposer.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.openejb.junit5;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(ApplicationComposerExtension.class)
+public @interface RunWithApplicationComposer {
+
+    /**
+     * The test container lifecycle <em>mode</em> to use.
+     */
+    ExtensionMode mode() default ExtensionMode.AUTO;
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AddInnerTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AddInnerTest.java
new file mode 100644
index 0000000..a0aacc3
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AddInnerTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.WebApp;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.inject.Inject;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@RunWithApplicationComposer
+public class AddInnerTest {
+    @Module
+    @Classes(innerClassesAsBean = true, cdi = true)
+    public WebApp web() {
+        return new WebApp();
+    }
+
+    @Inject
+    private Injectable notNull;
+
+    @Test
+    public void run() {
+        assertNotNull(notNull);
+    }
+
+    public static class Injectable {}
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerContextInjectionTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerContextInjectionTest.java
new file mode 100644
index 0000000..c83ec5c
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerContextInjectionTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.EnterpriseBean;
+import org.apache.openejb.jee.SingletonBean;
+import org.apache.openejb.rest.ThreadLocalContextManager;
+import org.apache.openejb.testing.AppResource;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.rest.ContextProvider;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.ws.rs.core.SecurityContext;
+import java.security.Principal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+@RunWithApplicationComposer(mode = ExtensionMode.PER_ALL)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class AppComposerContextInjectionTest {
+    @Module
+    public EnterpriseBean bean() {
+        return new SingletonBean(MyBean.class).localBean();
+    }
+
+    @AppResource
+    private Context context;
+
+    @AppResource
+    private ContextProvider provider;
+
+    @Test
+    public void lookupShouldWorkOnOpenEJBNames() throws NamingException {
+        assertEquals("ok", MyBean.class.cast(context.lookup("MyBeanLocalBean")).ok());
+    }
+
+    @Test
+    public void lookupShouldWorkOnGlobalNames() throws NamingException {
+        assertEquals("ok", MyBean.class.cast(context.lookup("java:global/AppComposerContextInjectionTest/bean/MyBean")).ok());
+    }
+
+    @Test
+    public void jaxrs() throws NamingException {
+        assertNotNull(provider);
+        assertNull(provider.find(SecurityContext.class));
+        final SecurityContext securityContext = new SecurityContext() {
+            @Override
+            public Principal getUserPrincipal() {
+                return null;
+            }
+
+            @Override
+            public boolean isUserInRole(final String s) {
+                return "foo".equals(s);
+            }
+
+            @Override
+            public boolean isSecure() {
+                return false;
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return null;
+            }
+        };
+        provider.register(SecurityContext.class, securityContext);
+        assertNotNull(provider.find(SecurityContext.class));
+        assertTrue(SecurityContext.class.cast(ThreadLocalContextManager.findThreadLocal(SecurityContext.class)).isUserInRole("foo"));
+        assertFalse(SecurityContext.class.cast(ThreadLocalContextManager.findThreadLocal(SecurityContext.class)).isUserInRole("bar"));
+    }
+
+    public static class MyBean {
+        public String ok() {
+            return "ok";
+        }
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerDynamicTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerDynamicTest.java
new file mode 100644
index 0000000..ff5fa0f
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerDynamicTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.WebApp;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@RunWithApplicationComposer
+public class AppComposerDynamicTest {
+
+    private static final Map<String, String> domainMap = new HashMap<>();
+
+    {
+        domainMap.put("www.somedomain.com","154.174.10.56");
+        domainMap.put("www.anotherdomain.com","211.152.104.132");
+        domainMap.put("www.yetanotherdomain.com","78.144.120.15");
+    }
+
+    @Inject
+    private DemoResolver resolver;
+
+    @Module
+    @Classes(innerClassesAsBean = true, cdi = true)
+    public WebApp web() {
+        return new WebApp();
+    }
+
+    @TestFactory
+    public Stream<DynamicTest> dynamicTestsFromStream() {
+
+        Set<String> input = domainMap.keySet();
+
+        return input.stream()
+                .map(dom -> DynamicTest.dynamicTest("Resolving: " + dom,
+                        () -> {
+                            assertEquals(domainMap.get(dom), resolver.resolve(dom));
+                        }));
+    }
+
+    public static class DemoResolver {
+        public String resolve(String domain) {
+            return domainMap.get(domain);
+        }
+
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerJarsTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerJarsTest.java
new file mode 100644
index 0000000..594d3d0
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerJarsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.itest.failover.ejb.Calculator;
+import org.apache.openejb.jee.WebApp;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Jars;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.ejb.EJB;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@RunWithApplicationComposer
+public class AppComposerJarsTest {
+    @Module
+    @Jars("failover-ejb-")
+    public WebApp war() {
+        return new WebApp();
+    }
+
+    @EJB
+    private Calculator calculator;
+
+    @Test
+    public void externalBeanFound() {
+        assertNotNull(calculator);
+        assertEquals(3, calculator.sum(1, 2));
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerOpenejbConfigTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerOpenejbConfigTest.java
new file mode 100644
index 0000000..b4db2f6
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerOpenejbConfigTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.AppResource;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@RunWithApplicationComposer
+public class AppComposerOpenejbConfigTest {
+    @Configuration
+    public String openejbXmlPath() {
+        return "custom-openejb.xml";
+    }
+
+    @Module
+    public EjbJar empty() {
+        return new EjbJar();
+    }
+
+    @AppResource
+    private Context ctx;
+
+    @Test
+    public void checkDsIsHere() throws NamingException {
+        assertNotNull(ctx.lookup("openejb:Resource/app-composer"));
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerWithModulesTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerWithModulesTest.java
new file mode 100644
index 0000000..11c09e0
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/AppComposerWithModulesTest.java
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import java.util.Properties;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.EJBException;
+import javax.ejb.LocalBean;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.naming.InitialContext;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.PersistenceContext;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import org.apache.openejb.jee.Empty;
+import org.apache.openejb.jee.StatelessBean;
+import org.apache.openejb.jee.jpa.unit.Persistence;
+import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
+import org.apache.openejb.junit5.ApplicationComposerPerEachExtension;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class AppComposerWithModulesTest {
+
+    @RegisterExtension
+    ApplicationComposerPerEachExtension ext = new ApplicationComposerPerEachExtension(new Modules());
+
+    @EJB
+    private PersistManager persistManager;
+
+    @Resource
+    private Validator validator;
+
+    @Resource
+    private ValidatorFactory validatorFactory;
+
+    @Configuration
+    public Properties config() {
+        final Properties p = new Properties();
+        p.put("bvalDatabase", "new://Resource?type=DataSource");
+        p.put("bvalDatabase.JdbcDriver", "org.hsqldb.jdbcDriver");
+        p.put("bvalDatabase.JdbcUrl", "jdbc:hsqldb:mem:bval");
+        return p;
+    }
+
+    public static class Modules {
+        @Module
+        public StatelessBean app() throws Exception {
+            final StatelessBean bean = new StatelessBean(PersistManager.class);
+            bean.setLocalBean(new Empty());
+            return bean;
+        }
+
+        @Module
+        public Persistence persistence() {
+            final PersistenceUnit unit = new PersistenceUnit("foo-unit");
+            unit.addClass(EntityToValidate.class);
+            unit.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
+            unit.getProperties().setProperty("openjpa.RuntimeUnenhancedClasses", "supported");
+            unit.setExcludeUnlistedClasses(true);
+
+            final Persistence persistence = new Persistence(unit);
+            persistence.setVersion("2.0");
+            return persistence;
+        }
+    }
+
+    @LocalBean
+    @Stateless
+    public static class PersistManager {
+        @PersistenceContext
+        private EntityManager em;
+
+        @Resource
+        private Validator validator;
+
+        @Resource
+        private ValidatorFactory validatorFactory;
+
+        public void persistValid() {
+            final EntityToValidate entity = new EntityToValidate();
+            entity.setName("name");
+            em.persist(entity);
+        }
+
+        public void persistNotValid() {
+            em.persist(new EntityToValidate());
+        }
+
+        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+        public Validator getValidator() {
+            return validator;
+        }
+
+        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
+        public ValidatorFactory getValidatorFactory() {
+            return validatorFactory;
+        }
+    }
+
+    @Entity
+    public static class EntityToValidate {
+        @Id
+        @GeneratedValue
+        private long id;
+
+        @NotNull
+        @Size(min = 1, max = 5)
+        private String name;
+
+        public long getId() {
+            return id;
+        }
+
+        public void setId(final long i) {
+            id = i;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String n) {
+            name = n;
+        }
+    }
+
+    @Test
+    public void valid() {
+        persistManager.persistValid();
+    }
+
+    @Test
+    public void notValid() {
+        try {
+            persistManager.persistNotValid();
+            fail();
+        } catch (final EJBException ejbException) {
+            assertTrue(ejbException.getCause() instanceof ConstraintViolationException);
+            final ConstraintViolationException constraintViolationException = (ConstraintViolationException) ejbException.getCause();
+            assertEquals(1, constraintViolationException.getConstraintViolations().size());
+        }
+    }
+
+    @Test
+    public void lookupValidatorFactory() throws Exception {
+        final ValidatorFactory validatorFactory = (ValidatorFactory) new InitialContext().lookup("java:comp/ValidatorFactory");
+        assertNotNull(validatorFactory);
+    }
+
+    @Test
+    public void lookupValidator() throws Exception {
+        final Validator validator = (Validator) new InitialContext().lookup("java:comp/Validator");
+        assertNotNull(validator);
+    }
+
+    @Test
+    public void injectionValidatorFactory() {
+        final ValidatorFactory validatorFactory = persistManager.getValidatorFactory();
+        assertNotNull(validatorFactory);
+    }
+
+    @Test
+    public void injectionValidator() {
+        final Validator validator = persistManager.getValidator();
+        assertNotNull(validator);
+    }
+
+    @Test
+    public void injection2ValidatorFactory() {
+        assertNotNull(validatorFactory);
+    }
+
+    @Test
+    public void injection2Validator() {
+        assertNotNull(validator);
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/PreDestroyTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/PreDestroyTest.java
new file mode 100644
index 0000000..b9c5966
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/PreDestroyTest.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.SessionBean;
+import org.apache.openejb.jee.SingletonBean;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.ejb.Singleton;
+import javax.inject.Inject;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@RunWithApplicationComposer
+public class PreDestroyTest {
+
+    private static final AtomicBoolean isConstructed = new AtomicBoolean(false);
+    private static final AtomicBoolean isDestroyed = new AtomicBoolean(false);
+
+    @Inject
+    private TestMe testMe;
+
+    @AfterAll
+    public static void onAfterClass() {
+        assertTrue(isConstructed.get(), "onPostConstruct was not called");
+        assertTrue(isDestroyed.get(), "onPreDestroy was not called");
+    }
+
+    @Module
+    public SessionBean getEjbs() {
+        return new SingletonBean(TestMe.class);
+    }
+
+    @Test
+    public void testLifecycle() {
+        this.testMe.noOp();
+    }
+
+    @Singleton
+    public static class TestMe {
+
+        @PostConstruct
+        public void onPostConstruct() {
+            isConstructed.set(true);
+        }
+
+        @PreDestroy
+        public void onPreDestroy() {
+            isDestroyed.set(true);
+        }
+
+        public void noOp() {
+            //no-op
+        }
+
+    }
+
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/RandomPortTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/RandomPortTest.java
new file mode 100644
index 0000000..4cb38a1
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/RandomPortTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.RandomPort;
+import org.junit.jupiter.api.Test;
+
+import java.net.URL;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@RunWithApplicationComposer
+public class RandomPortTest {
+    @Module
+    public EjbJar jar() {
+        return new EjbJar();
+    }
+
+    @RandomPort("httpejb")
+    private int port;
+
+    @RandomPort("httpejb")
+    private URL portUrl;
+
+    @Test
+    public void checkRandom() {
+        assertTrue(port > 0);
+        assertNotNull(portUrl);
+        assertEquals(port, portUrl.getPort());
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerJVMTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerJVMTest.java
new file mode 100644
index 0000000..dc34b17
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerJVMTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.junit5.app.MyApp;
+import org.apache.openejb.loader.SystemInstance;
+import org.junit.jupiter.api.Test;
+import org.apache.openejb.testing.Application;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+//-Djunit.jupiter.testclass.order.default=org.apache.openejb.junit5.order.AppComposerTestClassOrderer -Dtomee.application-composer.application=org.apache.openejb.junit5.app.MyApp
+@RunWithApplicationComposer(mode = ExtensionMode.PER_JVM)
+public class SingleAppComposerJVMTest {
+
+    @Application
+    private MyApp app;
+
+    @Test
+    public void run() {
+        assertNotNull(app);
+        app.check();
+        assertEquals("Set-Via-SingleAppComposerTest-In-Same-JVM", SystemInstance.get().getProperty("key"));
+    }
+
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerTest.java
new file mode 100644
index 0000000..152f0c5
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/SingleAppComposerTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.junit5.app.MyApp;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.testing.Application;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+//-Djunit.jupiter.testclass.order.default=org.apache.openejb.junit5.order.AppComposerTestClassOrderer -Dtomee.application-composer.application=org.apache.openejb.junit5.app.MyApp
+@RunWithApplicationComposer(mode = ExtensionMode.PER_JVM)
+public class SingleAppComposerTest {
+
+    @Application
+    private MyApp app;
+
+    @Test
+    public void run() {
+        assertNotNull(app);
+        app.check();
+        SystemInstance.get().setProperty("key", "Set-Via-SingleAppComposerTest-In-Same-JVM");
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebAppEnvEntryTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebAppEnvEntryTest.java
new file mode 100644
index 0000000..355b42e
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebAppEnvEntryTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.EnvEntry;
+import org.apache.openejb.jee.WebApp;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.inject.Inject;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@RunWithApplicationComposer
+public class WebAppEnvEntryTest {
+    @Module
+    @Classes(cdi = true, value = {CdiBean.class})
+    public WebApp war() {
+        final WebApp webApp = new WebApp().contextRoot("/myapp");
+        webApp.getEnvEntry().add(new EnvEntry("foo", String.class.getName(), "bar"));
+        return webApp;
+    }
+
+    @Inject
+    private CdiBean bean;
+
+    @Test
+    public void test() {
+        assertEquals("bar", bean.lookup());
+    }
+
+    public static class CdiBean {
+        public String lookup() {
+            try {
+                return String.class.cast(new InitialContext().lookup("java:comp/env/foo"));
+            } catch (final NamingException e) {
+                return "-";
+            }
+        }
+    }
+}
+
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebappMultipleModuleTest.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebappMultipleModuleTest.java
new file mode 100644
index 0000000..f753b82
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/WebappMultipleModuleTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5;
+
+import org.apache.openejb.jee.jpa.unit.Persistence;
+import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
+import org.apache.openejb.testing.CdiExtensions;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.PersistenceRootUrl;
+import org.junit.jupiter.api.Test;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Extension;
+import javax.inject.Inject;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@RunWithApplicationComposer
+@CdiExtensions(WebappMultipleModuleTest.SuperViciousExtension.class)
+@Classes(cdi = true, innerClassesAsBean = true)
+public class WebappMultipleModuleTest {
+    @Module
+    @PersistenceRootUrl(value = "")
+    public Persistence jpa() throws Exception {
+        SuperViciousExtension.CALLED.set(false); // reset before container boot
+        return new Persistence(new PersistenceUnit("jpa"));
+    }
+
+    @Inject
+    private Marker bean;
+
+    @Test
+    public void run() {
+        assertNotNull(bean);
+        assertTrue(SuperViciousExtension.CALLED.get());
+    }
+
+    public static class Marker {}
+
+    public static class SuperViciousExtension implements Extension {
+        public static final AtomicBoolean CALLED = new AtomicBoolean();
+
+        private void end(@Observes final AfterDeploymentValidation ignored , final BeanManager manager) {
+            final Bean<?> bean = manager.resolve(manager.getBeans(Marker.class));
+            assertNotNull(manager.getReference(bean, Marker.class, manager.createCreationalContext(bean)));
+            CALLED.set(true);
+        }
+    }
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/app/MyApp.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/app/MyApp.java
new file mode 100644
index 0000000..6ec52fd
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/app/MyApp.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5.app;
+
+import org.apache.openejb.testing.Application;
+import org.apache.openejb.testing.Classes;
+
+import javax.inject.Inject;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@Application
+@Classes(cdi = true, innerClassesAsBean = true)
+public class MyApp {
+
+    @Inject
+    private MyBean ok;
+
+    public void check() {
+        assertNotNull(ok);
+    }
+
+    public static class MyBean {
+    }
+
+}
diff --git a/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/order/AppComposerTestClassOrderer.java b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/order/AppComposerTestClassOrderer.java
new file mode 100644
index 0000000..1c8ae91
--- /dev/null
+++ b/container/openejb-junit5/src/test/java/org/apache/openejb/junit5/order/AppComposerTestClassOrderer.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openejb.junit5.order;
+
+import org.apache.openejb.junit5.SingleAppComposerJVMTest;
+import org.apache.openejb.junit5.SingleAppComposerTest;
+import org.junit.jupiter.api.ClassDescriptor;
+import org.junit.jupiter.api.ClassOrderer;
+import org.junit.jupiter.api.ClassOrdererContext;
+
+import java.util.Comparator;
+
+public class AppComposerTestClassOrderer implements ClassOrderer {
+
+    @Override
+    public void orderClasses(ClassOrdererContext classOrdererContext) {
+        classOrdererContext.getClassDescriptors().sort(Comparator.comparingInt(AppComposerTestClassOrderer::getOrder));
+    }
+
+    /*
+     * SingleAppComposerTest before SingleAppComposerJVMTest (same JVM test)
+     */
+    private static int getOrder(ClassDescriptor classDescriptor) {
+        if (classDescriptor.getTestClass().equals(SingleAppComposerTest.class)) {
+            return 2;
+        }
+
+        if (classDescriptor.getTestClass().equals(SingleAppComposerJVMTest.class)) {
+            return 3;
+        }
+
+        return 1;
+    }
+}
diff --git a/container/openejb-junit5/src/test/resources/custom-openejb.xml b/container/openejb-junit5/src/test/resources/custom-openejb.xml
new file mode 100644
index 0000000..9837b8c
--- /dev/null
+++ b/container/openejb-junit5/src/test/resources/custom-openejb.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<openejb>
+  <Resource id="app-composer" type="DataSource" />
+</openejb>
\ No newline at end of file
diff --git a/container/pom.xml b/container/pom.xml
index 9508572..e6197af 100644
--- a/container/pom.xml
+++ b/container/pom.xml
@@ -38,6 +38,7 @@
     <module>openejb-jee-accessors</module>
     <module>openejb-jpa-integration</module>
     <module>openejb-junit</module>
+    <module>openejb-junit5</module>
     <module>openejb-junit5-backward</module>
   </modules>
 </project>
diff --git a/examples/junit5-application-composer/README.adoc b/examples/junit5-application-composer/README.adoc
new file mode 100644
index 0000000..940a873
--- /dev/null
+++ b/examples/junit5-application-composer/README.adoc
@@ -0,0 +1,131 @@
+= JUnit 5: Application Composer
+:index-group: Testing Techniques
+:jbake-type: page
+:jbake-status: published
+
+The `org.apache.openejb.junit5.ApplicationComposerExtension` is a **JUnit 5 extension**  internally re-using `ApplicationComposers`, which has been used in the JUnit test runner ( `ApplicationComposer`) for testing OpenEJB in recent years. It involves no classpath scanning at all. If you want something to be in the app, you must build it directly in your testcase.
+
+With the `RunWithApplicationComposer` you can do identical testing that OpenEJB uses internally, but with limited dependency on OpenEJB itself.
+The main dependency is:
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.apache.tomee</groupId>
+  <artifactId>openejb-junit5</artifactId>
+  <version>${openejb.version}</version>
+</dependency>
+----
+
+Make sure to use a maven surefire version greater or equal to `3.0.0-M5` and add the required dependencies for JUnit 5.
+
+== Composing an Application
+
+The main difference to the embedded `EJBContainer` API is building the application in the test code.  This is done with one or more methods in the test case annotated
+with `org.apache.openejb.testing.Module` using the following format:
+
+[source,java]
+----
+@Module
+public <return-value> <module-name>() {
+----
+
+Where **module-name** is the name you wish to use for that module and **return-value** can be any one of the following:
+
+ - java.lang.Class
+ - java.lang.Class[]
+ - org.apache.openejb.jee.EjbJar
+ - org.apache.openejb.jee.EnterpriseBean
+ - org.apache.openejb.jee.Application
+ - org.apache.openejb.jee.Connector
+ - org.apache.openejb.jee.Beans
+ - org.apache.openejb.jee.jpa.unit.Persistence
+ - org.apache.openejb.jee.jpa.unit.PersistenceUnit
+
+== Example
+
+Used in an actual testcase, that might look like so:
+
+[source,java,numbered]
+----
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.StatefulBean;
+import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.transaction.UserTransaction;
+import java.util.List;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@RunWithApplicationComposer
+public class MoviesTest {
+
+    @EJB
+    private Movies movies;
+
+    @Resource
+    private UserTransaction userTransaction;
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    @Module
+    public PersistenceUnit persistence() {
+        PersistenceUnit unit = new PersistenceUnit("movie-unit");
+        unit.setJtaDataSource("movieDatabase");
+        unit.setNonJtaDataSource("movieDatabaseUnmanaged");
+        unit.getClazz().add(Movie.class.getName());
+        unit.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
+        return unit;
+    }
+
+    @Module
+    public EjbJar beans() {
+        EjbJar ejbJar = new EjbJar("movie-beans");
+        ejbJar.addEnterpriseBean(new StatefulBean(MoviesImpl.class));
+        return ejbJar;
+    }
+
+    @Configuration
+    public Properties config() throws Exception {
+        Properties p = new Properties();
+        p.put("movieDatabase", "new://Resource?type=DataSource");
+        p.put("movieDatabase.JdbcDriver", "org.hsqldb.jdbcDriver");
+        p.put("movieDatabase.JdbcUrl", "jdbc:hsqldb:mem:moviedb");
+        return p;
+    }
+
+    @Test
+    public void test() throws Exception {
+
+        userTransaction.begin();
+
+        try {
+            entityManager.persist(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
+            entityManager.persist(new Movie("Joel Coen", "Fargo", 1996));
+            entityManager.persist(new Movie("Joel Coen", "The Big Lebowski", 1998));
+
+            List<Movie> list = movies.getMovies();
+            assertEquals(3, list.size(), "List.size()");
+
+            for (Movie movie : list) {
+                movies.deleteMovie(movie);
+            }
+
+            assertEquals(0, movies.getMovies().size(), "Movies.getMovies()");
+
+        } finally {
+            userTransaction.commit();
+        }
+    }
+}
+----
diff --git a/examples/junit5-application-composer/pom.xml b/examples/junit5-application-composer/pom.xml
new file mode 100644
index 0000000..aff07d9
--- /dev/null
+++ b/examples/junit5-application-composer/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<!-- $Rev: 636494 $ $Date: 2008-03-12 21:24:02 +0100 (Wed, 12 Mar 2008) $ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.superbiz</groupId>
+  <artifactId>junit5-application-composer</artifactId>
+  <packaging>jar</packaging>
+  <version>8.0.7-SNAPSHOT</version>
+  <name>TomEE :: Examples :: JUnit 5 :: Application Composer</name>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  <build>
+    <defaultGoal>install</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>3.0.0-M5</version>
+      </plugin>
+      <plugin>
+        <groupId>org.tomitribe.transformer</groupId>
+        <artifactId>org.eclipse.transformer.maven</artifactId>
+        <version>0.1.1a</version>
+        <configuration>
+          <classifier>jakartaee9</classifier>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>package</phase>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <repositories>
+    <repository>
+      <id>apache-m2-snapshot</id>
+      <name>Apache Snapshot Repository</name>
+      <url>https://repository.apache.org/content/groups/snapshots</url>
+    </repository>
+  </repositories>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.tomee</groupId>
+      <artifactId>javaee-api</artifactId>
+      <version>[8.0,)</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <version>5.8.0-M1</version>
+      <scope>test</scope>
+    </dependency>
+    <!--
+    The <scope>test</scope> guarantees that non of your runtime
+    code is dependent on any OpenEJB classes.
+    -->
+    <dependency>
+      <groupId>org.apache.tomee</groupId>
+      <artifactId>openejb-core</artifactId>
+      <version>8.0.7-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomee</groupId>
+      <artifactId>openejb-junit5</artifactId>
+      <version>8.0.7-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <!--
+  This section allows you to configure where to publish libraries for sharing.
+  It is not required and may be deleted.  For more information see:
+  http://maven.apache.org/plugins/maven-deploy-plugin/
+  -->
+  <distributionManagement>
+    <repository>
+      <id>localhost</id>
+      <url>file://${basedir}/target/repo/</url>
+    </repository>
+    <snapshotRepository>
+      <id>localhost</id>
+      <url>file://${basedir}/target/snapshot-repo/</url>
+    </snapshotRepository>
+  </distributionManagement>
+</project>
diff --git a/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movie.java b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movie.java
new file mode 100644
index 0000000..b6bd4e8
--- /dev/null
+++ b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movie.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.superbiz.composed;
+
+import javax.persistence.Entity;
+
+@Entity
+public class Movie {
+
+    private String director;
+    private String title;
+    private int year;
+
+    public Movie() {
+    }
+
+    public Movie(String director, String title, int year) {
+        this.director = director;
+        this.title = title;
+        this.year = year;
+    }
+
+    public String getDirector() {
+        return director;
+    }
+
+    public void setDirector(String director) {
+        this.director = director;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public int getYear() {
+        return year;
+    }
+
+    public void setYear(int year) {
+        this.year = year;
+    }
+
+}
diff --git a/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movies.java b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movies.java
new file mode 100644
index 0000000..456aa75
--- /dev/null
+++ b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/Movies.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.superbiz.composed;
+
+import java.util.List;
+
+/**
+ * @version $Revision: 607077 $ $Date: 2007-12-27 06:55:23 -0800 (Thu, 27 Dec 2007) $
+ */
+public interface Movies {
+
+    void addMovie(Movie movie) throws Exception;
+
+    void deleteMovie(Movie movie) throws Exception;
+
+    List<Movie> getMovies() throws Exception;
+}
diff --git a/examples/junit5-application-composer/src/main/java/org/superbiz/composed/MoviesImpl.java b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/MoviesImpl.java
new file mode 100644
index 0000000..4e126fb
--- /dev/null
+++ b/examples/junit5-application-composer/src/main/java/org/superbiz/composed/MoviesImpl.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.superbiz.composed;
+
+import javax.ejb.Stateful;
+import javax.ejb.TransactionAttribute;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.PersistenceContextType;
+import javax.persistence.Query;
+import java.util.List;
+
+import static javax.ejb.TransactionAttributeType.MANDATORY;
+
+//START SNIPPET: code
+@Stateful(name = "Movies")
+@TransactionAttribute(MANDATORY)
+public class MoviesImpl implements Movies {
+
+    @PersistenceContext(unitName = "movie-unit", type = PersistenceContextType.TRANSACTION)
+    private EntityManager entityManager;
+
+    public void addMovie(Movie movie) throws Exception {
+        entityManager.persist(movie);
+    }
+
+    public void deleteMovie(Movie movie) throws Exception {
+        entityManager.remove(movie);
+    }
+
+    public List<Movie> getMovies() throws Exception {
+        Query query = entityManager.createQuery("SELECT m from Movie as m");
+        return query.getResultList();
+    }
+}
+//END SNIPPET: code
diff --git a/examples/junit5-application-composer/src/test/java/org/superbiz/composed/MoviesTest.java b/examples/junit5-application-composer/src/test/java/org/superbiz/composed/MoviesTest.java
new file mode 100644
index 0000000..568873c
--- /dev/null
+++ b/examples/junit5-application-composer/src/test/java/org/superbiz/composed/MoviesTest.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.superbiz.composed;
+
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.StatefulBean;
+import org.apache.openejb.jee.jpa.unit.PersistenceUnit;
+import org.apache.openejb.junit5.RunWithApplicationComposer;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.transaction.UserTransaction;
+import java.util.List;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+//START SNIPPET: code
+@RunWithApplicationComposer
+public class MoviesTest {
+
+    @EJB
+    private Movies movies;
+
+    @Resource
+    private UserTransaction userTransaction;
+
+    @PersistenceContext
+    private EntityManager entityManager;
+
+    @Module
+    public PersistenceUnit persistence() {
+        PersistenceUnit unit = new PersistenceUnit("movie-unit");
+        unit.setJtaDataSource("movieDatabase");
+        unit.setNonJtaDataSource("movieDatabaseUnmanaged");
+        unit.getClazz().add(Movie.class.getName());
+        unit.setProperty("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
+        return unit;
+    }
+
+    @Module
+    public EjbJar beans() {
+        EjbJar ejbJar = new EjbJar("movie-beans");
+        ejbJar.addEnterpriseBean(new StatefulBean(MoviesImpl.class));
+        return ejbJar;
+    }
+
+    @Configuration
+    public Properties config() throws Exception {
+        Properties p = new Properties();
+        p.put("movieDatabase", "new://Resource?type=DataSource");
+        p.put("movieDatabase.JdbcDriver", "org.hsqldb.jdbcDriver");
+        p.put("movieDatabase.JdbcUrl", "jdbc:hsqldb:mem:moviedb");
+        return p;
+    }
+
+    @Test
+    public void test() throws Exception {
+
+        userTransaction.begin();
+
+        try {
+            entityManager.persist(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
+            entityManager.persist(new Movie("Joel Coen", "Fargo", 1996));
+            entityManager.persist(new Movie("Joel Coen", "The Big Lebowski", 1998));
+
+            List<Movie> list = movies.getMovies();
+            assertEquals(3, list.size(), "List.size()");
+
+            for (Movie movie : list) {
+                movies.deleteMovie(movie);
+            }
+
+            assertEquals(0, movies.getMovies().size(), "Movies.getMovies()");
+
+        } finally {
+            userTransaction.commit();
+        }
+    }
+}
+//END SNIPPET: code
diff --git a/examples/pom.xml b/examples/pom.xml
index 88cebda..46b75e0 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -96,6 +96,7 @@
     <module>jpa-enumerated</module>
     <module>jsf-managedBean-and-ejb</module>
     <module>jsf-cdi-and-ejb</module>
+    <module>junit5-application-composer</module>
     <module>lookup-of-ejbs</module>
     <module>lookup-of-ejbs-with-descriptor</module>
     <!-- Does not work with Java 9+ because of the modules. Needs more work
diff --git a/pom.xml b/pom.xml
index f875209..5c84bec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,8 +97,7 @@
     <tomee.version>${project.version}</tomee.version>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
-    <surefire.version>2.21.0</surefire.version>
-    <surefire.junit5.version>3.0.0-M5</surefire.junit5.version>
+    <surefire.version>3.0.0-M5</surefire.version>
 
     <!-- for the default name of the module. Needs to be overridden -->
     <tomee.build.name>${project.groupId}.${project.artifactId}</tomee.build.name>
@@ -116,7 +115,7 @@
     <geronimo-javamail_1.6_mail.version>1.0.0</geronimo-javamail_1.6_mail.version>
 
     <openjpa.version>3.1.2</openjpa.version>
-    <openwebbeans.version>2.0.22-SNAPSHOT</openwebbeans.version>
+    <openwebbeans.version>2.0.22</openwebbeans.version>
     <jcs.version>2.1</jcs.version>
     <johnzon.version>1.2.10</johnzon.version>
     <quartz-openejb-shade.version>2.2.4</quartz-openejb-shade.version>
@@ -191,7 +190,7 @@
     <org.apache.activemq.version>5.16.1</org.apache.activemq.version>
     <org.springframework.version>3.1.4.RELEASE</org.springframework.version>
     <junit.version>4.13.1</junit.version>
-    <junit.jupiter.version>5.7.0</junit.jupiter.version>
+    <junit.jupiter.version>5.8.0-M1</junit.jupiter.version>
     <org.apache.axis2.version>1.4.1</org.apache.axis2.version>
     <scannotation.version>1.0.2</scannotation.version>
     <geronimo.connector.version>3.1.4</geronimo.connector.version>
@@ -1102,6 +1101,24 @@
         <scope>test</scope>
       </dependency>
       <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-api</artifactId>
+        <version>${junit.jupiter.version}</version>
+        <scope>provided</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-engine</artifactId>
+        <version>${junit.jupiter.version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.vintage</groupId>
+        <artifactId>junit-vintage-engine</artifactId>
+        <version>${junit.jupiter.version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
         <groupId>com.agical.rmock</groupId>
         <artifactId>rmock</artifactId>
         <version>2.0.2</version>
diff --git a/tomee/tomee-embedded/pom.xml b/tomee/tomee-embedded/pom.xml
index 1650151..7558398 100644
--- a/tomee/tomee-embedded/pom.xml
+++ b/tomee/tomee-embedded/pom.xml
@@ -65,6 +65,7 @@
               <artifactSet>
                 <excludes>
                   <exclude>junit:junit</exclude>
+                  <exclude>org.junit.jupiter:junit-jupiter-api</exclude>
                   <exclude>*:jaxb-api</exclude>
                   <exclude>*:jaxb-impl</exclude>
                 </excludes>
@@ -81,7 +82,7 @@
               <shadedClassifierName>jaxrs</shadedClassifierName>
               <artifactSet>
                 <excludes>
-                  <exclude>junit:junit</exclude>
+                  <exclude>org.junit.jupiter:junit-jupiter-api</exclude>
                   <exclude>*:jaxb-api</exclude>
                   <exclude>*:jaxb-impl</exclude>
                   <exclude>org.apache.activemq:*</exclude>
@@ -132,6 +133,7 @@
               <artifactSet>
                 <excludes>
                   <exclude>junit:junit</exclude>
+                  <exclude>org.junit.jupiter:junit-jupiter-api</exclude>
                   <exclude>*:jaxb-api</exclude>
                   <exclude>*:jaxb-impl</exclude>
                   <exclude>org.apache.activemq:*</exclude>
@@ -210,7 +212,9 @@
             <configuration>
               <excludes>
                 <exclude>**/SingleInstanceRunnerTest*</exclude>
+                <exclude>**/SingleInstanceRunnerExtensionTest*</exclude>
                 <exclude>**/NoScannerSingleRunnerTest*</exclude>
+                <exclude>**/NoScannerSingleRunnerExtensionTest*</exclude>
               </excludes>
             </configuration>
           </execution>
@@ -423,6 +427,22 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <!-- Required for running JUnit 4 via JUnit 5 environment -->
+    <dependency>
+      <groupId>org.junit.vintage</groupId>
+      <artifactId>junit-vintage-engine</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.eclipse.jdt.core.compiler</groupId>
       <artifactId>ecj</artifactId>
       <type>jar</type>
@@ -480,5 +500,6 @@
       <version>1.60</version>
       <scope>test</scope>
     </dependency>
+
   </dependencies>
 </project>
diff --git a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedBase.java b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedBase.java
new file mode 100644
index 0000000..48dcf47
--- /dev/null
+++ b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedBase.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.embedded.junit;
+
+import org.apache.tomee.embedded.TomEEEmbeddedApplicationRunner;
+
+import javax.enterprise.inject.Vetoed;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Vetoed
+public class TomEEEmbeddedBase {
+    private static final AtomicReference<TomEEEmbeddedApplicationRunner> RUNNER = new AtomicReference<>();
+
+    public void start(final Object marker) throws Exception {
+        getRunner().start(marker.getClass(), (Properties) null);
+    }
+
+    public void close() {
+        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
+        if (runner != null) {
+            runner.close();
+        }
+    }
+
+    public void setApp(final Object o) {
+        getRunner().setApp(o);
+    }
+
+    public void composerInject(final Object target) throws IllegalAccessException {
+        getRunner().composerInject(target);
+    }
+
+    private TomEEEmbeddedApplicationRunner getRunner() {
+        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
+        if (runner == null) {
+            RUNNER.compareAndSet(null, new TomEEEmbeddedApplicationRunner());
+        }
+        return RUNNER.get();
+    }
+
+}
diff --git a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedSingleRunner.java b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedSingleRunner.java
index 6151a12..9b2864f 100644
--- a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedSingleRunner.java
+++ b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/TomEEEmbeddedSingleRunner.java
@@ -16,7 +16,6 @@
  */
 package org.apache.tomee.embedded.junit;
 
-import org.apache.tomee.embedded.TomEEEmbeddedApplicationRunner;
 import org.junit.rules.MethodRule;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -28,31 +27,29 @@
 
 import javax.enterprise.inject.Vetoed;
 import java.util.List;
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * see org.apache.tomee.embedded.SingleInstanceRunnerTest for a sample.
  * idea is to reuse some part of ApplicationComposer API to get a single container for all tests in embedded mode.
- *
+ * <p>
  * Base is to declare an @Application class which holds the model and some injections.
  * Note: this can be replaced setting tomee.application-composer.application property to the fully qualified name of the app.
  * Note: @Application classes are only searched in the same jar as the test.
- *
+ * <p>
  * Model:
  * - @Configuration: programmatic properties - note injections don't work there.
  * - @Classes: only context value is used.
  * - @ContainerProperties: to configure the container
  * - @WebResource: first value can be used to set the docBase (other values are ignored)
  * - @TomEEEmbeddedSingleRunner.LifecycleTasks: allow to add some lifecycle tasks (like starting a ftp/sft/elasticsearch... server)
- *
+ * <p>
  * Injections:
  * - CDI
  * - @RandomPort: with the value http or https. Supported types are URL (context base) and int (the port).
  */
 @Vetoed
 public class TomEEEmbeddedSingleRunner extends BlockJUnit4ClassRunner {
-    private static final AtomicReference<TomEEEmbeddedApplicationRunner> RUNNER = new AtomicReference<>();
+    private static final TomEEEmbeddedBase BASE = new TomEEEmbeddedBase();
 
     // use when you use another runner like Parameterized of JUnit
     public static class Rule implements TestRule {
@@ -67,8 +64,8 @@
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
-                    start(test);
-                    RUNNER.get().composerInject(test);
+                    BASE.start(test);
+                    BASE.composerInject(test);
                     base.evaluate();
                 }
             };
@@ -78,31 +75,17 @@
     public static class Start extends RunListener {
         @Override
         public void testStarted(final Description description) throws Exception {
-            start(null);
+            BASE.start(null);
         }
     }
 
-    private static void start(final Object marker) throws Exception {
-        getRunner().start(marker.getClass(), (Properties) null);
-    }
-
     public static void setApp(final Object o) {
-        getRunner().setApp(o);
+        BASE.setApp(o);
     }
 
-    private static TomEEEmbeddedApplicationRunner getRunner() {
-        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
-        if (runner == null) {
-            RUNNER.compareAndSet(null, new TomEEEmbeddedApplicationRunner());
-        }
-        return RUNNER.get();
-    }
 
     public static void close() {
-        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
-        if (runner != null) {
-            runner.close();
-        }
+        BASE.close();
     }
 
     public TomEEEmbeddedSingleRunner(final Class<?> klass) throws InitializationError {
@@ -118,8 +101,8 @@
                 return new Statement() {
                     @Override
                     public void evaluate() throws Throwable {
-                        start(test);
-                        getRunner().composerInject(target);
+                        BASE.start(test);
+                        BASE.composerInject(target);
                         base.evaluate();
                     }
                 };
diff --git a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/RunWithTomEEEmbedded.java b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/RunWithTomEEEmbedded.java
new file mode 100644
index 0000000..79bf105
--- /dev/null
+++ b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/RunWithTomEEEmbedded.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomee.embedded.junit.jupiter;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(org.apache.tomee.embedded.junit.jupiter.TomEEEmbeddedExtension.class)
+public @interface RunWithTomEEEmbedded {
+
+}
diff --git a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/TomEEEmbeddedExtension.java b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/TomEEEmbeddedExtension.java
new file mode 100644
index 0000000..106555f
--- /dev/null
+++ b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/junit/jupiter/TomEEEmbeddedExtension.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.embedded.junit.jupiter;
+
+import org.apache.openejb.OpenEJBRuntimeException;
+import org.apache.tomee.embedded.junit.TomEEEmbeddedBase;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestInstances;
+
+import java.util.List;
+
+public class TomEEEmbeddedExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
+
+    private static final TomEEEmbeddedBase BASE = new TomEEEmbeddedBase();
+
+    @Override
+    public void afterAll(ExtensionContext context) {
+        if (isPerClass(context)) {
+            BASE.close();
+        }
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext context) throws Exception {
+
+        if (isPerClass(context)) {
+            BASE.start(context.getRequiredTestInstance());
+            doInject(context);
+        }
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) throws Exception {
+        if (!isPerClass(context)) {
+            BASE.close();
+        }
+    }
+
+    @Override
+    public void beforeEach(ExtensionContext context) throws Exception {
+        if (!isPerClass(context)) {
+            BASE.start(context.getRequiredTestInstance());
+            doInject(context);
+        }
+    }
+
+    private void doInject(final ExtensionContext extensionContext) {
+        TestInstances oTestInstances = extensionContext.getTestInstances()
+                .orElseThrow(() -> new OpenEJBRuntimeException("No test instances available for the given extension context."));
+
+        List<Object> testInstances = oTestInstances.getAllInstances();
+
+        testInstances.forEach(t -> {
+            try {
+                BASE.composerInject(t);
+            } catch (Exception e) {
+                throw new OpenEJBRuntimeException(e);
+            }
+        });
+    }
+
+    boolean isPerClass(final ExtensionContext context) {
+        return context.getTestInstanceLifecycle()
+                .map(it -> it.equals(TestInstance.Lifecycle.PER_CLASS))
+                .orElse(false);
+    }
+}
diff --git a/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/NoScannerSingleRunnerExtensionTest.java b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/NoScannerSingleRunnerExtensionTest.java
new file mode 100644
index 0000000..129dd1f
--- /dev/null
+++ b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/NoScannerSingleRunnerExtensionTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.embedded;
+
+import org.apache.openejb.testing.Application;
+import org.apache.openejb.testing.Classes;
+import org.apache.tomee.embedded.junit.jupiter.RunWithTomEEEmbedded;
+import org.apache.tomee.embedded.junit.jupiter.TomEEEmbeddedExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+// just a manual test to check it works, can't be executed with the rest of the suite,
+// we could use a different surefire execution if we want to add it to the default run
+//-Dtomee.application-composer.application=org.apache.tomee.embedded.NoScannerSingleRunnerExtensionTest$ScanApp
+@RunWithTomEEEmbedded
+public class NoScannerSingleRunnerExtensionTest {
+    @Application // app can have several injections/helpers
+    private ScanApp app;
+
+    @Test
+    public void run() {
+        assertNotNull(app);
+        app.check();
+    }
+
+    @Application
+    @Classes(value = ScanMe.class)
+    public static class ScanApp {
+        @Inject
+        private ScanMe ok;
+
+        @Inject
+        private Instance<NotScanned> ko;
+
+        public void check() {
+            assertNotNull(ok);
+            assertTrue(ko.isUnsatisfied());
+        }
+    }
+
+    @ApplicationScoped
+    public static class ScanMe {
+    }
+
+    @ApplicationScoped
+    public static class NotScanned {
+    }
+}
diff --git a/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/SingleInstanceRunnerExtensionTest.java b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/SingleInstanceRunnerExtensionTest.java
new file mode 100644
index 0000000..00445be
--- /dev/null
+++ b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/SingleInstanceRunnerExtensionTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.embedded;
+
+import org.apache.openejb.assembler.classic.Assembler;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.observer.Observes;
+import org.apache.openejb.testing.Application;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.ContainerProperties;
+import org.apache.openejb.testing.RandomPort;
+import org.apache.openejb.testng.PropertiesBuilder;
+import org.apache.tomee.embedded.event.TomEEEmbeddedApplicationRunnerInjection;
+import org.apache.tomee.embedded.junit.jupiter.RunWithTomEEEmbedded;
+import org.apache.tomee.embedded.junit.jupiter.TomEEEmbeddedExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Properties;
+
+import static org.junit.Assert.*;
+
+// just a manual test to check it works, can't be executed with the rest of the suite,
+// we could use a different surefire execution if we want to add it to the default run
+//-Dtomee.application-composer.application=org.apache.tomee.embedded.SingleInstanceRunnerExtensionTest$TheApp
+@RunWithTomEEEmbedded
+public class SingleInstanceRunnerExtensionTest {
+    @Application // app can have several injections/helpers
+    private TheApp app;
+
+    @RandomPort("http") // @RandomPort are propagated by value + type only (both need to match ATM)
+    private int port;
+
+    @Test
+    public void run() {
+        assertNotNull(SystemInstance.get().getComponent(Assembler.class));
+        assertEquals("val", SystemInstance.get().getProperty("simple"));
+        assertEquals("set", SystemInstance.get().getProperty("t"));
+        assertEquals("p", SystemInstance.get().getProperty("prog"));
+        assertEquals("128463", SystemInstance.get().getProperty("my.server.port"));
+        assertEquals("true", SystemInstance.get().getProperty("configurer"));
+        assertNotEquals(8080, app.port);
+        assertTrue(app.base.toExternalForm().endsWith("/app"));
+        assertEquals(app.port, port);
+        assertNotNull(app.task);
+        assertNotNull(app.tasks);
+        assertEquals(1, app.tasks.size());
+        assertEquals(app.task, app.tasks.iterator().next());
+        assertEquals(app.task, MyTask.instance);
+        assertNotNull(app.custom);
+    }
+
+    @Application
+    @Classes(context = "app")
+    @ContainerProperties({
+            @ContainerProperties.Property(name = "simple", value = "val"),
+            @ContainerProperties.Property(name = "tomee.embedded.application.runner.properties.t", value = "${t.value}"),
+            @ContainerProperties.Property(name = "tomee.embedded.application.runner.t.value", value = "set")
+    })
+    @TomEEEmbeddedApplicationRunner.LifecycleTasks(MyTask.class)
+    // can start a ftp/sftp/elasticsearch/mongo/... server before tomee
+    @TomEEEmbeddedApplicationRunner.Configurers(SetMyProperty.class)
+    public static class TheApp {
+        @RandomPort("http")
+        private int port;
+
+        @RandomPort("http")
+        private URL base;
+
+        @TomEEEmbeddedApplicationRunner.LifecycleTask
+        private MyTask task;
+
+        @TomEEEmbeddedApplicationRunner.LifecycleTask
+        private Collection<LifecycleTask> tasks;
+
+        @org.apache.openejb.testing.Configuration
+        public Properties add() {
+            return new PropertiesBuilder().p("prog", "p").build();
+        }
+
+        private Custom custom;
+
+        public void doInject(@Observes final TomEEEmbeddedApplicationRunnerInjection injector) {
+            injector.inject(Custom.class, new Custom())
+                    .inject(NotHere.class, new NotHere());
+        }
+    }
+
+    public static class NotHere {
+    }
+
+    public static class Custom {
+    }
+
+    public static class MyTask implements LifecycleTask {
+        private static MyTask instance;
+
+        @Override
+        public Closeable beforeContainerStartup() {
+            instance = this;
+            System.out.println(">>> start");
+            System.setProperty("my.server.port", "128463");
+            return new Closeable() {
+                @Override
+                public void close() throws IOException {
+                    System.out.println(">>> close");
+                }
+            };
+        }
+    }
+
+    public static class SetMyProperty implements TomEEEmbeddedApplicationRunner.Configurer {
+        @Override
+        public void configure(final Configuration configuration) {
+            configuration.property("configurer", "true");
+        }
+    }
+}