You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2015/02/22 22:13:23 UTC

tomee git commit: TOMEE-1518 ContainerRule and ApplicationRule

Repository: tomee
Updated Branches:
  refs/heads/develop e79c813e2 -> 7ab0188cd


TOMEE-1518 ContainerRule and ApplicationRule


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/7ab0188c
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/7ab0188c
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/7ab0188c

Branch: refs/heads/develop
Commit: 7ab0188cd06c4b74ce9079299d4a58c76f18ac24
Parents: e79c813
Author: Romain Manni-Bucau <rm...@apache.org>
Authored: Sun Feb 22 22:13:11 2015 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
Committed: Sun Feb 22 22:13:11 2015 +0100

----------------------------------------------------------------------
 .../openejb/junit/ApplicationComposerRule.java  |   8 +
 .../openejb/testing/ApplicationComposers.java   | 750 ++++++++++---------
 .../org/apache/openejb/testing/Classes.java     |   2 +
 .../org/apache/openejb/util/StringTemplate.java |   2 +-
 4 files changed, 407 insertions(+), 355 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/7ab0188c/container/openejb-core/src/main/java/org/apache/openejb/junit/ApplicationComposerRule.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/junit/ApplicationComposerRule.java b/container/openejb-core/src/main/java/org/apache/openejb/junit/ApplicationComposerRule.java
index b804a8e..79207ec 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/junit/ApplicationComposerRule.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/junit/ApplicationComposerRule.java
@@ -35,6 +35,14 @@ public class ApplicationComposerRule implements TestRule {
         }
     }
 
+    public <T> T getInstance(final Class<T> as) {
+        return as.cast(instance);
+    }
+
+    public Object[] getModules() {
+        return modules;
+    }
+
     @Override
     public Statement apply(final Statement base, final Description description) {
         return new DeployApplication(instance, base, new ApplicationComposers(instance.getClass(), modules));

http://git-wip-us.apache.org/repos/asf/tomee/blob/7ab0188c/container/openejb-core/src/main/java/org/apache/openejb/testing/ApplicationComposers.java
----------------------------------------------------------------------
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 20d24fc..108dd26 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
@@ -90,7 +90,10 @@ import org.apache.xbean.finder.archive.Archive;
 import org.apache.xbean.finder.archive.ClassesArchive;
 import org.apache.xbean.finder.archive.CompositeArchive;
 import org.apache.xbean.finder.archive.FileArchive;
+import org.apache.xbean.finder.archive.FilteredArchive;
 import org.apache.xbean.finder.archive.JarArchive;
+import org.apache.xbean.finder.filter.Filter;
+import org.apache.xbean.finder.filter.Filters;
 import org.xml.sax.InputSource;
 
 import java.io.File;
@@ -123,14 +126,16 @@ import javax.enterprise.inject.spi.Extension;
 import javax.inject.Inject;
 import javax.naming.Context;
 import javax.naming.InitialContext;
+import javax.naming.NamingException;
 
 import static java.util.Arrays.asList;
 import static org.apache.openejb.config.DeploymentFilterable.DEPLOYMENTS_CLASSPATH_PROPERTY;
 import static org.apache.openejb.loader.JarLocation.jarLocation;
 import static org.apache.openejb.util.Classes.ancestors;
 
+// TODO: surely explode it and base it on refactored ContainerRule and DeployApplicationRule
 @SuppressWarnings("deprecation")
-public final class ApplicationComposers {
+public class ApplicationComposers {
 
     public static final String OPENEJB_APPLICATION_COMPOSER_CONTEXT = "openejb.application.composer.context";
     private static final Class[] MODULE_TYPES = {IAnnotationFinder.class, ClassesArchive.class,
@@ -147,7 +152,7 @@ public final class ApplicationComposers {
     }
 
     private final Map<Object, ClassFinder> testClassFinders;
-    private final Class<?> testClass;
+    private Class<?> testClass;
     private ServiceManagerProxy serviceManager;
 
     // invocation context
@@ -177,89 +182,99 @@ public final class ApplicationComposers {
         }
 
         validate();
+        assembler = SystemInstance.get().getComponent(Assembler.class); // for DeployApplicationRule we need it
+    }
+
+    protected boolean isContainer() {
+        return true;
+    }
+
+    protected boolean isApplication() {
+        return true;
     }
 
     private void validate() {
         final List<Throwable> errors = new ArrayList<>();
 
-        final Map<Object, List<Method>> annotatedConfigurationMethods = findAnnotatedMethods(new HashMap<Object, List<Method>>(), Configuration.class);
-        {
-            int nbProp = 0;
-            int nbOpenejb = 0;
-            for (final List<Method> list : annotatedConfigurationMethods.values()) {
-                for (final Method m : list) {
-                    final Class<?> type = m.getReturnType();
-                    if (Openejb.class.isAssignableFrom(type) || String.class.equals(type)) {
-                        nbOpenejb++;
-                    } else if (Properties.class.isAssignableFrom(type)) {
-                        nbProp++;
-                    } // else not supported?
+        if (isContainer()) {
+            final Map<Object, List<Method>> annotatedConfigurationMethods = findAnnotatedMethods(new HashMap<Object, List<Method>>(), Configuration.class);
+            {
+                int nbProp = 0;
+                int nbOpenejb = 0;
+                for (final List<Method> list : annotatedConfigurationMethods.values()) {
+                    for (final Method m : list) {
+                        final Class<?> type = m.getReturnType();
+                        if (Openejb.class.isAssignableFrom(type) || String.class.equals(type)) {
+                            nbOpenejb++;
+                        } else if (Properties.class.isAssignableFrom(type)) {
+                            nbProp++;
+                        } // else not supported?
+                    }
+                }
+                if (nbProp > 1 || nbOpenejb > 1) {
+                    final String gripe = "Test class should have no more than one @Configuration method by type (Openejb/String or Properties)";
+                    errors.add(new Exception(gripe));
                 }
             }
-            if (nbProp > 1 || nbOpenejb > 1) {
-                final String gripe = "Test class should have no more than one @Configuration method by type (Openejb/String or Properties)";
-                errors.add(new Exception(gripe));
-            }
-        }
-
-        int injectorSize = 0;
-        for (final List<Method> m : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.MockInjector.class).values()) {
-            injectorSize += m.size();
-        }
-        for (final List<Method> m : findAnnotatedMethods(new HashMap<Object, List<Method>>(), MockInjector.class).values()) {
-            injectorSize += m.size();
-        }
-        if (injectorSize > 1) {
-            errors.add(new Exception("Test class should have no more than one @MockInjector method"));
-        }
 
-        final List<Method> components = new ArrayList<>();
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Component.class).values()) {
-            components.addAll(l);
-        }
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Component.class).values()) {
-            components.addAll(l);
-        }
-        for (final Method method : components) {
-            if (method.getParameterTypes().length > 0) {
-                errors.add(new Exception("@Component methods shouldn't take any parameters"));
+            int injectorSize = 0;
+            for (final List<Method> m : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.MockInjector.class).values()) {
+                injectorSize += m.size();
+            }
+            for (final List<Method> m : findAnnotatedMethods(new HashMap<Object, List<Method>>(), MockInjector.class).values()) {
+                injectorSize += m.size();
+            }
+            if (injectorSize > 1) {
+                errors.add(new Exception("Test class should have no more than one @MockInjector method"));
             }
-        }
 
-        final List<Method> descriptors = new ArrayList<>();
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Descriptors.class).values()) {
-            descriptors.addAll(l);
-        }
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Descriptors.class).values()) {
-            descriptors.addAll(l);
-        }
-        for (final Method method : descriptors) {
-            final Class<?> returnType = method.getReturnType();
-            if (!returnType.equals(WebModule.class) && !returnType.equals(EjbModule.class)
-                && !returnType.equals(WebApp.class) && !returnType.equals(EjbJar.class)
-                && !returnType.equals(AppModule.class)) {
-                errors.add(new Exception("@Descriptors can't be used on " + returnType.getName()));
+            final List<Method> components = new ArrayList<>();
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Component.class).values()) {
+                components.addAll(l);
+            }
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Component.class).values()) {
+                components.addAll(l);
+            }
+            for (final Method method : components) {
+                if (method.getParameterTypes().length > 0) {
+                    errors.add(new Exception("@Component methods shouldn't take any parameters"));
+                }
+            }
+            for (final ClassFinder finder : testClassFinders.values()) {
+                for (final Field field : finder.findAnnotatedFields(RandomPort.class)) {
+                    final Class<?> type = field.getType();
+                    if (int.class != type && URL.class != type) {
+                        throw new IllegalArgumentException("@RandomPort is only supported for int fields");
+                    }
+                }
             }
         }
 
-        final List<Method> classes = new ArrayList<>();
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Classes.class).values()) {
-            classes.addAll(l);
-        }
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Classes.class).values()) {
-            classes.addAll(l);
-        }
-        for (final Method method : classes) {
-            final Class<?> returnType = method.getReturnType();
-            if (!returnType.equals(WebModule.class) && !returnType.equals(EjbModule.class)
-                && !returnType.equals(WebApp.class) && !returnType.equals(EjbJar.class)
-                && !EnterpriseBean.class.isAssignableFrom(returnType)) {
-                errors.add(new Exception("@Classes can't be used on a method returning " + returnType));
+        if (isApplication()) {
+            final List<Method> descriptors = new ArrayList<>();
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Descriptors.class).values()) {
+                descriptors.addAll(l);
+            }
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Descriptors.class).values()) {
+                descriptors.addAll(l);
+            }
+            for (final Method method : descriptors) {
+                final Class<?> returnType = method.getReturnType();
+                if (!returnType.equals(WebModule.class) && !returnType.equals(EjbModule.class)
+                        && !returnType.equals(WebApp.class) && !returnType.equals(EjbJar.class)
+                        && !returnType.equals(AppModule.class)) {
+                    errors.add(new Exception("@Descriptors can't be used on " + returnType.getName()));
+                }
             }
-        }
 
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Jars.class).values()) {
-            for (final Method method : l) {
+            final List<Method> classes = new ArrayList<>();
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Classes.class).values()) {
+                classes.addAll(l);
+            }
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Classes.class).values()) {
+                classes.addAll(l);
+            }
+            for (final Method method : classes) {
                 final Class<?> returnType = method.getReturnType();
                 if (!returnType.equals(WebModule.class) && !returnType.equals(EjbModule.class)
                         && !returnType.equals(WebApp.class) && !returnType.equals(EjbJar.class)
@@ -267,51 +282,52 @@ public final class ApplicationComposers {
                     errors.add(new Exception("@Classes can't be used on a method returning " + returnType));
                 }
             }
-        }
 
-        int appModules = 0;
-        int modules = 0;
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Jars.class).values()) {
+                for (final Method method : l) {
+                    final Class<?> returnType = method.getReturnType();
+                    if (!returnType.equals(WebModule.class) && !returnType.equals(EjbModule.class)
+                            && !returnType.equals(WebApp.class) && !returnType.equals(EjbJar.class)
+                            && !EnterpriseBean.class.isAssignableFrom(returnType)) {
+                        errors.add(new Exception("@Classes can't be used on a method returning " + returnType));
+                    }
+                }
+            }
 
-        final List<Method> moduleMethods = new ArrayList<>();
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Module.class).values()) {
-            moduleMethods.addAll(l);
-        }
-        for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Module.class).values()) {
-            moduleMethods.addAll(l);
-        }
-        for (final Method method : moduleMethods) {
+            int appModules = 0;
+            int modules = 0;
 
-            modules++;
+            final List<Method> moduleMethods = new ArrayList<>();
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Module.class).values()) {
+                moduleMethods.addAll(l);
+            }
+            for (final List<Method> l : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Module.class).values()) {
+                moduleMethods.addAll(l);
+            }
+            for (final Method method : moduleMethods) {
 
-            final Class<?> type = method.getReturnType();
+                modules++;
 
-            if (Application.class.isAssignableFrom(type)) {
+                final Class<?> type = method.getReturnType();
 
-                appModules++;
+                if (Application.class.isAssignableFrom(type)) {
 
-            } else if (!isValidModuleType(type, MODULE_TYPES)) {
-                final String gripe = "@Module method must return " + Join.join(" or ", MODULE_TYPES).replaceAll("(class|interface) ", "");
-                errors.add(new Exception(gripe));
-            }
-        }
-
-        for (final ClassFinder finder : testClassFinders.values()) {
-            for (final Field field : finder.findAnnotatedFields(RandomPort.class)) {
-                final Class<?> type = field.getType();
-                if (int.class != type && URL.class != type) {
-                    throw new IllegalArgumentException("@RandomPort is only supported for int fields");
+                    appModules++;
+                } else if (!isValidModuleType(type, MODULE_TYPES)) {
+                    final String gripe = "@Module method must return " + Join.join(" or ", MODULE_TYPES).replaceAll("(class|interface) ", "");
+                    errors.add(new Exception(gripe));
                 }
             }
-        }
 
-        if (appModules > 1) {
-            final String gripe = "Test class should have no more than one @Module method that returns " + Application.class.getName();
-            errors.add(new Exception(gripe));
-        }
+            if (appModules > 1) {
+                final String gripe = "Test class should have no more than one @Module method that returns " + Application.class.getName();
+                errors.add(new Exception(gripe));
+            }
 
-        if (modules < 1 && testClass.getAnnotation(Classes.class) == null && testClass.getAnnotation(Default.class) == null) {
-            final String gripe = "Test class should have at least one @Module method";
-            errors.add(new Exception(gripe));
+            if (modules < 1 && testClass.getAnnotation(Classes.class) == null && testClass.getAnnotation(Default.class) == null) {
+                final String gripe = "Test class should have at least one @Module method";
+                errors.add(new Exception(gripe));
+            }
         }
 
         if (!errors.isEmpty()) {
@@ -348,6 +364,15 @@ public final class ApplicationComposers {
         // we hacked testInstance while we were not aware of it, now we can solve it
         testClassFinders.put(inputTestInstance, testClassFinders.remove(this));
 
+        startContainer(inputTestInstance);
+
+        servletContext = new MockServletContext();
+        session = new MockHttpSession();
+
+        deployApp(inputTestInstance);
+    }
+
+    public void deployApp(final Object inputTestInstance) throws Exception {
         final ClassLoader loader = testClass.getClassLoader();
         AppModule appModule = new AppModule(loader, testClass.getSimpleName());
 
@@ -373,154 +398,6 @@ public final class ApplicationComposers {
             appModule.getEjbModules().add(ejbModule);
         }
 
-        // For the moment we just take the first @Configuration method
-        // maybe later we can add something fancy to allow multiple configurations using a qualifier
-        // as a sort of altDD/altConfig concept.  Say for example the altDD prefix might be "foo",
-        // we can then imagine something like this:
-        // @Foo @Configuration public Properties alternateConfig(){...}
-        // @Foo @Module  public Properties alternateModule(){...}
-        // anyway, one thing at a time ....
-
-        final Properties configuration = new Properties();
-        configuration.put(DEPLOYMENTS_CLASSPATH_PROPERTY, "false");
-
-        final EnableServices annotation = testClass.getAnnotation(EnableServices.class);
-        if (annotation != null && annotation.httpDebug()) {
-            configuration.setProperty("httpejbd.print", "true");
-            configuration.setProperty("httpejbd.indent.xml", "true");
-            configuration.setProperty("logging.level.OpenEJB.server.http", "FINE");
-        }
-        final org.apache.openejb.junit.EnableServices annotationOld = testClass.getAnnotation(org.apache.openejb.junit.EnableServices.class);
-        if (annotationOld != null && annotationOld.httpDebug()) {
-            configuration.setProperty("httpejbd.print", "true");
-            configuration.setProperty("httpejbd.indent.xml", "true");
-            configuration.setProperty("logging.level.OpenEJB.server.http", "FINE");
-        }
-        final WebResource webResource = testClass.getAnnotation(WebResource.class);
-        if (webResource != null && webResource.value().length > 0) {
-            configuration.setProperty("openejb.embedded.http.resources", Join.join(",", webResource.value()));
-        }
-
-        Openejb openejb = null;
-        final Map<Object, List<Method>> configs = new HashMap<>();
-        findAnnotatedMethods(configs, Configuration.class);
-        findAnnotatedMethods(configs, org.apache.openejb.junit.Configuration.class);
-        for (final Map.Entry<Object, List<Method>> method : configs.entrySet()) {
-            for (final Method m : method.getValue()) {
-                final Object o = m.invoke(method.getKey());
-                if (o instanceof Properties) {
-                    final Properties properties = (Properties) o;
-                    configuration.putAll(properties);
-                } else if (Openejb.class.isInstance(o)) {
-                    openejb = Openejb.class.cast(o);
-                } else if (String.class.isInstance(o)) {
-                    final String path = String.class.cast(o);
-                    final URL url = Thread.currentThread().getContextClassLoader().getResource(path);
-                    if (url == null) {
-                        throw new IllegalArgumentException(o.toString() + " not found");
-                    }
-                    final InputStream in = url.openStream();
-                    try {
-                        if (path.endsWith(".json")) {
-                            openejb = JSonConfigReader.read(Openejb.class, in);
-                        } else {
-                            openejb = JaxbOpenejb.readConfig(new InputSource(in));
-                        }
-                    } finally {
-                        IO.close(in);
-                    }
-                }
-            }
-        }
-
-        if (SystemInstance.isInitialized()) {
-            SystemInstance.reset();
-        }
-
-        Collection<String> propertiesToSetAgain = null;
-        final ContainerProperties configAnnot = testClass.getAnnotation(ContainerProperties.class);
-        if (configAnnot != null) {
-            for (final ContainerProperties.Property p : configAnnot.value()) {
-                final String value = p.value();
-                final String name = p.name();
-                configuration.put(name, value.equals(ContainerProperties.Property.IGNORED) ? null : value);
-                if (value.contains("${")) {
-                    if (propertiesToSetAgain == null) {
-                        propertiesToSetAgain = new LinkedList<>();
-                    }
-                    propertiesToSetAgain.add(name);
-                }
-            }
-        }
-
-        SystemInstance.init(configuration);
-        for (final Map.Entry<Object, ClassFinder> finder : testClassFinders.entrySet()) {
-            for (final Field field : finder.getValue().findAnnotatedFields(RandomPort.class)) {
-                if (!field.isAccessible()) {
-                    field.setAccessible(true);
-                }
-
-                final String service = field.getAnnotation(RandomPort.class).value();
-                final String key = ("http".equals(service) ? "httpejbd" : service) + ".port";
-                final String existing = SystemInstance.get().getProperty(key);
-                final int random;
-                if (existing == null) {
-                    random = NetworkUtil.getNextAvailablePort();
-                    SystemInstance.get().setProperty(key, Integer.toString(random));
-                } else {
-                    random = Integer.parseInt(existing);
-                }
-                if (int.class == field.getType()) {
-                    field.set(finder.getKey(), random);
-                } else if (URL.class == field.getType()) {
-                    field.set(finder.getKey(), new URL("http://localhost:" + random + "/"));
-                }
-            }
-        }
-
-        for (final Map.Entry<Object, ClassFinder> finder : testClassFinders.entrySet()) {
-            if (!finder.getValue().findAnnotatedClasses(SimpleLog.class).isEmpty()) {
-                SystemInstance.get().setProperty("openejb.jul.forceReload", "true");
-                break;
-            }
-        }
-
-        final CdiExtensions cdiExtensions = testClass.getAnnotation(CdiExtensions.class);
-        if (cdiExtensions != null) {
-            SystemInstance.get().setComponent(LoaderService.class, new ExtensionAwareOptimizedLoaderService(cdiExtensions.value()));
-        }
-
-        // save the test under test to be able to retrieve it from extensions
-        // /!\ has to be done before all other init
-        SystemInstance.get().setComponent(TestInstance.class, new TestInstance(testClass, inputTestInstance));
-
-        // call the mock injector before module method to be able to use mocked classes
-        // it will often use the TestInstance so
-        final Map<Object, List<Method>> mockInjectors = new HashMap<>();
-        findAnnotatedMethods(mockInjectors, MockInjector.class);
-        findAnnotatedMethods(mockInjectors, org.apache.openejb.junit.MockInjector.class);
-        if (!mockInjectors.isEmpty() && !mockInjectors.values().iterator().next().isEmpty()) {
-            final Map.Entry<Object, List<Method>> methods = mockInjectors.entrySet().iterator().next();
-            Object o = methods.getValue().iterator().next().invoke(methods.getKey());
-            if (o instanceof Class<?>) {
-                o = ((Class<?>) o).newInstance();
-            }
-            if (o instanceof FallbackPropertyInjector) {
-                SystemInstance.get().setComponent(FallbackPropertyInjector.class, (FallbackPropertyInjector) o);
-            }
-        }
-
-        for (final Map.Entry<Object, List<Method>> method : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Component.class).entrySet()) {
-            for (final Method m : method.getValue()) {
-                setComponent(method.getKey(), m);
-            }
-        }
-        for (final Map.Entry<Object, List<Method>> method : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Component.class).entrySet()) {
-            for (final Method m : method.getValue()) {
-                setComponent(method.getKey(), m);
-            }
-        }
-
         final Map<String, URL> additionalDescriptors = descriptorsToMap(testClass.getAnnotation(org.apache.openejb.junit.Descriptors.class));
         final Map<String, URL> additionalDescriptorsNew = descriptorsToMap(testClass.getAnnotation(Descriptors.class));
         additionalDescriptors.putAll(additionalDescriptorsNew);
@@ -544,6 +421,7 @@ public final class ApplicationComposers {
                 final boolean defaultConfig = method.getAnnotation(Default.class) != null;
 
                 Class<?>[] classes = null;
+                String[] excludes = null;
                 Class<?>[] cdiInterceptors = null;
                 Class<?>[] cdiAlternatives = null;
                 Class<?>[] cdiDecorators = null;
@@ -551,6 +429,7 @@ public final class ApplicationComposers {
                 boolean innerClassesAsBean = false;
                 if (classesAnnotation != null) {
                     classes = classesAnnotation.value();
+                    excludes = classesAnnotation.excludes();
                     innerClassesAsBean = classesAnnotation.innerClassesAsBean();
                     cdiInterceptors = classesAnnotation.cdiInterceptors();
                     cdiDecorators = classesAnnotation.cdiDecorators();
@@ -572,7 +451,7 @@ public final class ApplicationComposers {
                         method.getAnnotation(Descriptors.class), method.getAnnotation(JaxrsProviders.class),
                         webApp,
                         globalJarsAnnotation, jarsAnnotation,
-                        classes, cdiInterceptors, cdiAlternatives, cdiDecorators, cdi, innerClassesAsBean,
+                        classes, excludes, cdiInterceptors, cdiAlternatives, cdiDecorators, cdi, innerClassesAsBean,
                         defaultConfig);
                 } else if (obj instanceof WebModule) { // will add the ejbmodule too
                     webModulesNb++;
@@ -592,7 +471,7 @@ public final class ApplicationComposers {
                     if (defaultConfig) {
                         (files == null ? files = new LinkedList<>() : files).add(jarLocation(testClass));
                     }
-                    webModule.setFinder(finderFromClasses(webModule, classes, files));
+                    webModule.setFinder(finderFromClasses(webModule, classes, files, excludes));
                     ejbModule.setFinder(webModule.getFinder());
                 } else if (obj instanceof EjbModule) {
                     final EjbModule ejbModule = (EjbModule) obj;
@@ -610,7 +489,7 @@ public final class ApplicationComposers {
                     if (defaultConfig) {
                         (files == null ? files = new LinkedList<>() : files).add(jarLocation(testClass));
                     }
-                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files));
+                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files, excludes));
                 } else if (obj instanceof EjbJar) {
 
                     final EjbJar ejbJar = (EjbJar) obj;
@@ -630,7 +509,7 @@ public final class ApplicationComposers {
                     if (defaultConfig) {
                         (files == null ? files = new LinkedList<>() : files).add(jarLocation(testClass));
                     }
-                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files));
+                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files, excludes));
                 } else if (obj instanceof EnterpriseBean) {
 
                     final EnterpriseBean bean = (EnterpriseBean) obj;
@@ -648,7 +527,7 @@ public final class ApplicationComposers {
                     if (defaultConfig) {
                         (files == null ? files = new LinkedList<>() : files).add(jarLocation(testClass));
                     }
-                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files));
+                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files, excludes));
                 } else if (obj instanceof Application) {
 
                     application = (Application) obj;
@@ -683,7 +562,7 @@ public final class ApplicationComposers {
                     if (defaultConfig) {
                         (files == null ? files = new LinkedList<>() : files).add(jarLocation(testClass));
                     }
-                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files));
+                    ejbModule.setFinder(finderFromClasses(ejbModule, classes, files, excludes));
                 } else if (obj instanceof Class[]) {
 
                     final Class[] beans = (Class[]) obj;
@@ -743,7 +622,7 @@ public final class ApplicationComposers {
             addWebApp(
                     appModule, testBean, additionalDescriptors,
                     null, null,
-                    webapp, globalJarsAnnotation, null, classClasses.value(),
+                    webapp, globalJarsAnnotation, null, classClasses.value(), classClasses.excludes(),
                     classClasses.cdiInterceptors(), classClasses.cdiAlternatives(), classClasses.cdiDecorators(),
                     classClasses.cdi(), classClasses.innerClassesAsBean(), testClass.getAnnotation(Default.class) != null);
             webModulesNb++;
@@ -791,66 +670,15 @@ public final class ApplicationComposers {
             }
         }
 
-        if (webModulesNb > 0 && SystemInstance.get().getComponent(WebAppBuilder.class) == null) {
-            SystemInstance.get().setComponent(WebAppBuilder.class, new LightweightWebAppBuilder());
-        }
-
         if (moduleMethods.size() == 1 && webModulesNb == 1) {
             appModule.setStandloneWebModule();
         }
 
-        final ConfigurationFactory config = new ConfigurationFactory();
-        config.init(SystemInstance.get().getProperties());
-
-        assembler = new Assembler();
-        SystemInstance.get().setComponent(Assembler.class, assembler);
-
-        final OpenEjbConfiguration openEjbConfiguration;
-        if (openejb != null) {
-            openEjbConfiguration = config.getOpenEjbConfiguration(openejb);
-        } else {
-            openEjbConfiguration = config.getOpenEjbConfiguration();
-        }
-        assembler.buildContainerSystem(openEjbConfiguration);
-
-        if ("true".equals(configuration.getProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "false"))
-            || annotation != null || annotationOld != null) {
-            try {
-                if (annotation != null) {
-                    final List<String> value = new ArrayList<String>(asList(annotation.value()));
-                    if (annotation.jaxrs()) {
-                        value.add("jaxrs");
-                    }
-                    if (annotation.jaxws()) {
-                        value.add("jaxws");
-                    }
-                    initFilteredServiceManager(value.toArray(new String[value.size()]));
-                }
-                if (annotationOld != null) {
-                    initFilteredServiceManager(annotationOld.value());
-                }
-                serviceManager = new ServiceManagerProxy(false);
-                serviceManager.start();
-            } catch (final ServiceManagerProxy.AlreadyStartedException e) {
-                throw new OpenEJBRuntimeException(e);
-            }
-        }
-
-        if (propertiesToSetAgain != null) {
-            for (final String name : propertiesToSetAgain) {
-                final String value = PropertyPlaceHolderHelper.simpleValue(SystemInstance.get().getProperty(name));
-                configuration.put(name, value);
-                System.setProperty(name, value); // done lazily to support placeholders so container will not do it here
-            }
-            propertiesToSetAgain.clear();
+        if (webModulesNb > 0 && SystemInstance.get().getComponent(WebAppBuilder.class) == null) {
+            SystemInstance.get().setComponent(WebAppBuilder.class, new LightweightWebAppBuilder());
         }
 
-        servletContext = new MockServletContext();
-        session = new MockHttpSession();
-
-        final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
-        final Context jndiContext = containerSystem.getJNDIContext();
-
+        final Context jndiContext = SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext();
         for (final EnvEntry entry : testBean.getEnvEntry()) { // set it in global jndi context since that's "app" entries and otherwise when we are no more in test bean context lookup fails
             final String name = entry.getName();
             final String jndi;
@@ -862,15 +690,14 @@ public final class ApplicationComposers {
             jndiContext.bind(jndi, entry.getEnvEntryValue());
         }
 
-        appInfo = config.configureApplication(appModule);
-
+        appInfo = SystemInstance.get().getComponent(ConfigurationFactory.class).configureApplication(appModule);
         appContext = assembler.createApplication(appInfo);
 
         if (mockCdiContexts()) {
             ScopeHelper.startContexts(appContext.getWebBeansContext().getContextsService(), servletContext, session);
         }
 
-        final BeanContext context = containerSystem.getBeanContext(testClass.getName());
+        final BeanContext context = SystemInstance.get().getComponent(ContainerSystem.class).getBeanContext(testClass.getName());
 
         enrich(inputTestInstance, context);
 
@@ -915,7 +742,7 @@ public final class ApplicationComposers {
         testClassFinders.put(this, testClassFinder);
     }
 
-    private boolean mockCdiContexts() {
+    protected boolean mockCdiContexts() {
         return "true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.testing.start-cdi-contexts", "true"));
     }
 
@@ -926,6 +753,7 @@ public final class ApplicationComposers {
                           final WebApp webapp, final Jars globalJarsAnnotation,
                           final Jars jarsAnnotation,
                           final Class<?>[] cdiClasses,
+                          final String[] excludes,
                           final Class<?>[] cdiInterceptors,
                           final Class<?>[] cdiAlternatives,
                           final Class<?>[] cdiDecorators,
@@ -1023,7 +851,7 @@ public final class ApplicationComposers {
             }
         }
 
-        final IAnnotationFinder finder = finderFromClasses(webModule, classes, libs);
+        final IAnnotationFinder finder = finderFromClasses(webModule, classes, libs, excludes);
         webModule.setFinder(finder);
         ejbModule.setFinder(webModule.getFinder());
         if (providersClasses != null) {
@@ -1042,6 +870,10 @@ public final class ApplicationComposers {
     }
 
     private void enrich(final Object inputTestInstance, final BeanContext context) throws org.apache.openejb.OpenEJBException {
+        if (context == null) {
+            return;
+        }
+
         final ThreadContext callContext = new ThreadContext(context, null, Operation.INJECTION);
         final ThreadContext oldContext = ThreadContext.enter(callContext);
         try {
@@ -1163,40 +995,10 @@ public final class ApplicationComposers {
         try {
             runAll(beforeDestroyAfterRunnables);
             if (assembler != null) {
-                final ContextsService contextsService = appContext.getWebBeansContext().getContextsService();
-                contextsService.endContext(SessionScoped.class, session);
-                contextsService.endContext(RequestScoped.class, null);
-                contextsService.endContext(ConversationScoped.class, null);
-
-                try {
-                    assembler.destroyApplication(appInfo.path);
-                } catch (final Exception e) {
-                    // no-op
-                }
-
-                final ContainerSystem component = SystemInstance.get().getComponent(ContainerSystem.class);
-
-                if (null != component) {
-                    final Context context = component.getJNDIContext();
-
-                    for (final String entry : globalJndiEntries) {
-                        context.unbind(entry);
-                    }
-                }
-
-                globalJndiEntries.clear();
-
-                if (mockCdiContexts()) {
-                    try {
-                        ScopeHelper.stopContexts(contextsService, servletContext, session);
-                    } catch (final Exception e) {
-                        // no-op
-                    }
-                }
+                stopApplication();
             }
 
             if (serviceManager != null) {
-
                 try {
                     serviceManager.stop();
                 } catch (final RuntimeException ignored) {
@@ -1210,6 +1012,43 @@ public final class ApplicationComposers {
         }
     }
 
+    public void stopApplication() throws NamingException {
+        if (appContext != null) {
+            final ContextsService contextsService = appContext.getWebBeansContext().getContextsService();
+            contextsService.endContext(SessionScoped.class, session);
+            contextsService.endContext(RequestScoped.class, null);
+            contextsService.endContext(ConversationScoped.class, null);
+        }
+
+        if (appInfo != null) {
+            try {
+                assembler.destroyApplication(appInfo.path);
+            } catch (final Exception e) {
+                // no-op
+            }
+        }
+
+        final ContainerSystem component = SystemInstance.get().getComponent(ContainerSystem.class);
+
+        if (null != component) {
+            final Context context = component.getJNDIContext();
+
+            for (final String entry : globalJndiEntries) {
+                context.unbind(entry);
+            }
+        }
+
+        globalJndiEntries.clear();
+
+        if (mockCdiContexts() && appContext != null) {
+            try {
+                ScopeHelper.stopContexts(appContext.getWebBeansContext().getContextsService(), servletContext, session);
+            } catch (final Exception e) {
+                // no-op
+            }
+        }
+    }
+
     private void runAll(final Collection<Runnable> runnables) {
         for (final Runnable r : runnables) {
             try {
@@ -1285,23 +1124,27 @@ public final class ApplicationComposers {
             }
             return dds;
         }
-        return new HashMap<String, URL>();
+        return new HashMap<>();
     }
 
-    private static IAnnotationFinder finderFromClasses(final DeploymentModule module, final Class<?>[] value, final Collection<File> others) {
+    private IAnnotationFinder finderFromClasses(final DeploymentModule module, final Class<?>[] value, final Collection<File> others, final String[] excludes) {
         final Collection<Archive> archives = new ArrayList<>(1 + (others == null ? 0 : others.size()));
 
+        final Filter filter = excludes == null || excludes.length == 0 ? null : Filters.invert(Filters.prefixes(excludes));
+
         final Collection<Class<?>> classes = new ArrayList<>(asList(FinderFactory.ensureMinimalClasses(module)));
         if (value != null) {
             classes.addAll(asList(value));
         }
-        archives.add(new ClassesArchive(classes));
+        final ClassesArchive classesArchive = new ClassesArchive(classes);
+        archives.add(filter == null ? classesArchive : new FilteredArchive(classesArchive, filter));
 
         if (others != null) {
             final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
             for (final File f : others) {
                 try {
-                    archives.add(f.isDirectory() ? new FileArchive(classLoader, f) : new JarArchive(classLoader, f.toURI().toURL()));
+                    final Archive archive = f.isDirectory() ? new FileArchive(classLoader, f) : new JarArchive(classLoader, f.toURI().toURL());
+                    archives.add(filter == null ? archive : new FilteredArchive(archive, filter));
                 } catch (final MalformedURLException e) {
                     throw new IllegalArgumentException(e);
                 }
@@ -1341,6 +1184,205 @@ public final class ApplicationComposers {
         }
     }
 
+    public void startContainer(final Object instance) throws Exception {
+        testClassFinders.remove(this); // see constructor
+
+        // For the moment we just take the first @Configuration method
+        // maybe later we can add something fancy to allow multiple configurations using a qualifier
+        // as a sort of altDD/altConfig concept.  Say for example the altDD prefix might be "foo",
+        // we can then imagine something like this:
+        // @Foo @Configuration public Properties alternateConfig(){...}
+        // @Foo @Module  public Properties alternateModule(){...}
+        // anyway, one thing at a time ....
+
+        final Properties configuration = new Properties();
+        configuration.put(DEPLOYMENTS_CLASSPATH_PROPERTY, "false");
+
+        final EnableServices annotation = testClass.getAnnotation(EnableServices.class);
+        if (annotation != null && annotation.httpDebug()) {
+            configuration.setProperty("httpejbd.print", "true");
+            configuration.setProperty("httpejbd.indent.xml", "true");
+            configuration.setProperty("logging.level.OpenEJB.server.http", "FINE");
+        }
+        final org.apache.openejb.junit.EnableServices annotationOld = testClass.getAnnotation(org.apache.openejb.junit.EnableServices.class);
+        if (annotationOld != null && annotationOld.httpDebug()) {
+            configuration.setProperty("httpejbd.print", "true");
+            configuration.setProperty("httpejbd.indent.xml", "true");
+            configuration.setProperty("logging.level.OpenEJB.server.http", "FINE");
+        }
+        final WebResource webResource = testClass.getAnnotation(WebResource.class);
+        if (webResource != null && webResource.value().length > 0) {
+            configuration.setProperty("openejb.embedded.http.resources", Join.join(",", webResource.value()));
+        }
+
+        Openejb openejb = null;
+        final Map<Object, List<Method>> configs = new HashMap<>();
+        findAnnotatedMethods(configs, Configuration.class);
+        findAnnotatedMethods(configs, org.apache.openejb.junit.Configuration.class);
+        for (final Map.Entry<Object, List<Method>> method : configs.entrySet()) {
+            for (final Method m : method.getValue()) {
+                final Object o = m.invoke(method.getKey());
+                if (o instanceof Properties) {
+                    final Properties properties = (Properties) o;
+                    configuration.putAll(properties);
+                } else if (Openejb.class.isInstance(o)) {
+                    openejb = Openejb.class.cast(o);
+                } else if (String.class.isInstance(o)) {
+                    final String path = String.class.cast(o);
+                    final URL url = Thread.currentThread().getContextClassLoader().getResource(path);
+                    if (url == null) {
+                        throw new IllegalArgumentException(o.toString() + " not found");
+                    }
+                    final InputStream in = url.openStream();
+                    try {
+                        if (path.endsWith(".json")) {
+                            openejb = JSonConfigReader.read(Openejb.class, in);
+                        } else {
+                            openejb = JaxbOpenejb.readConfig(new InputSource(in));
+                        }
+                    } finally {
+                        IO.close(in);
+                    }
+                }
+            }
+        }
+
+        if (SystemInstance.isInitialized()) {
+            SystemInstance.reset();
+        }
+
+        Collection<String> propertiesToSetAgain = null;
+        final ContainerProperties configAnnot = testClass.getAnnotation(ContainerProperties.class);
+        if (configAnnot != null) {
+            for (final ContainerProperties.Property p : configAnnot.value()) {
+                final String value = p.value();
+                final String name = p.name();
+                configuration.put(name, ContainerProperties.Property.IGNORED.equals(value) ? null : value);
+                if (value.contains("${")) {
+                    if (propertiesToSetAgain == null) {
+                        propertiesToSetAgain = new LinkedList<>();
+                    }
+                    propertiesToSetAgain.add(name);
+                }
+            }
+        }
+
+        SystemInstance.init(configuration);
+        for (final Map.Entry<Object, ClassFinder> finder : testClassFinders.entrySet()) {
+            for (final Field field : finder.getValue().findAnnotatedFields(RandomPort.class)) {
+                if (!field.isAccessible()) {
+                    field.setAccessible(true);
+                }
+
+                final String service = field.getAnnotation(RandomPort.class).value();
+                final String key = ("http".equals(service) ? "httpejbd" : service) + ".port";
+                final String existing = SystemInstance.get().getProperty(key);
+                final int random;
+                if (existing == null) {
+                    random = NetworkUtil.getNextAvailablePort();
+                    SystemInstance.get().setProperty(key, Integer.toString(random));
+                } else {
+                    random = Integer.parseInt(existing);
+                }
+                if (int.class == field.getType()) {
+                    field.set(finder.getKey(), random);
+                } else if (URL.class == field.getType()) {
+                    field.set(finder.getKey(), new URL("http://localhost:" + random + "/"));
+                }
+            }
+        }
+
+        for (final Map.Entry<Object, ClassFinder> finder : testClassFinders.entrySet()) {
+            if (!finder.getValue().findAnnotatedClasses(SimpleLog.class).isEmpty()) {
+                SystemInstance.get().setProperty("openejb.jul.forceReload", "true");
+                break;
+            }
+        }
+
+        final CdiExtensions cdiExtensions = testClass.getAnnotation(CdiExtensions.class);
+        if (cdiExtensions != null) {
+            SystemInstance.get().setComponent(LoaderService.class, new ExtensionAwareOptimizedLoaderService(cdiExtensions.value()));
+        }
+
+        // save the test under test to be able to retrieve it from extensions
+        // /!\ has to be done before all other init
+        SystemInstance.get().setComponent(TestInstance.class, new TestInstance(testClass, instance));
+
+        // call the mock injector before module method to be able to use mocked classes
+        // it will often use the TestInstance so
+        final Map<Object, List<Method>> mockInjectors = new HashMap<>();
+        findAnnotatedMethods(mockInjectors, MockInjector.class);
+        findAnnotatedMethods(mockInjectors, org.apache.openejb.junit.MockInjector.class);
+        if (!mockInjectors.isEmpty() && !mockInjectors.values().iterator().next().isEmpty()) {
+            final Map.Entry<Object, List<Method>> methods = mockInjectors.entrySet().iterator().next();
+            Object o = methods.getValue().iterator().next().invoke(methods.getKey());
+            if (o instanceof Class<?>) {
+                o = ((Class<?>) o).newInstance();
+            }
+            if (o instanceof FallbackPropertyInjector) {
+                SystemInstance.get().setComponent(FallbackPropertyInjector.class, (FallbackPropertyInjector) o);
+            }
+        }
+
+        for (final Map.Entry<Object, List<Method>> method : findAnnotatedMethods(new HashMap<Object, List<Method>>(), Component.class).entrySet()) {
+            for (final Method m : method.getValue()) {
+                setComponent(method.getKey(), m);
+            }
+        }
+        for (final Map.Entry<Object, List<Method>> method : findAnnotatedMethods(new HashMap<Object, List<Method>>(), org.apache.openejb.junit.Component.class).entrySet()) {
+            for (final Method m : method.getValue()) {
+                setComponent(method.getKey(), m);
+            }
+        }
+
+        final ConfigurationFactory config = new ConfigurationFactory();
+        config.init(SystemInstance.get().getProperties());
+        SystemInstance.get().setComponent(ConfigurationFactory.class, config);
+
+        assembler = new Assembler();
+        SystemInstance.get().setComponent(Assembler.class, assembler);
+
+        final OpenEjbConfiguration openEjbConfiguration;
+        if (openejb != null) {
+            openEjbConfiguration = config.getOpenEjbConfiguration(openejb);
+        } else {
+            openEjbConfiguration = config.getOpenEjbConfiguration();
+        }
+        assembler.buildContainerSystem(openEjbConfiguration);
+
+        if ("true".equals(configuration.getProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "false"))
+                || annotation != null || annotationOld != null) {
+            try {
+                if (annotation != null) {
+                    final List<String> value = new ArrayList<>(asList(annotation.value()));
+                    if (annotation.jaxrs()) {
+                        value.add("jaxrs");
+                    }
+                    if (annotation.jaxws()) {
+                        value.add("jaxws");
+                    }
+                    initFilteredServiceManager(value.toArray(new String[value.size()]));
+                }
+                if (annotationOld != null) {
+                    initFilteredServiceManager(annotationOld.value());
+                }
+                serviceManager = new ServiceManagerProxy(false);
+                serviceManager.start();
+            } catch (final ServiceManagerProxy.AlreadyStartedException e) {
+                throw new OpenEJBRuntimeException(e);
+            }
+        }
+
+        if (propertiesToSetAgain != null) {
+            for (final String name : propertiesToSetAgain) {
+                final String value = PropertyPlaceHolderHelper.simpleValue(SystemInstance.get().getProperty(name));
+                configuration.put(name, value);
+                System.setProperty(name, value); // done lazily to support placeholders so container will not do it here
+            }
+            propertiesToSetAgain.clear();
+        }
+    }
+
     protected static class ExtensionAwareOptimizedLoaderService extends OptimizedLoaderService {
         private final Class<? extends Extension>[] extensions;
 
@@ -1432,7 +1474,7 @@ public final class ApplicationComposers {
 
         final Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(args[0]);
 
-        final List<String> a = new ArrayList<String>(asList(args));
+        final List<String> a = new ArrayList<>(asList(args));
         a.remove(0);
         run(c, a.toArray(new String[a.size()]));
     }

http://git-wip-us.apache.org/repos/asf/tomee/blob/7ab0188c/container/openejb-core/src/main/java/org/apache/openejb/testing/Classes.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/testing/Classes.java b/container/openejb-core/src/main/java/org/apache/openejb/testing/Classes.java
index 8eaadca..3806f42 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/testing/Classes.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/testing/Classes.java
@@ -27,6 +27,8 @@ import java.lang.annotation.Target;
 public @interface Classes {
     Class<?>[] value() default {};
 
+    String[] excludes() default {};
+
     Class<?>[] cdiInterceptors() default {};
 
     Class<?>[] cdiDecorators() default {};

http://git-wip-us.apache.org/repos/asf/tomee/blob/7ab0188c/container/openejb-core/src/main/java/org/apache/openejb/util/StringTemplate.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/util/StringTemplate.java b/container/openejb-core/src/main/java/org/apache/openejb/util/StringTemplate.java
index f57fe5f..0d0a442 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/util/StringTemplate.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/util/StringTemplate.java
@@ -54,7 +54,7 @@ public class StringTemplate {
             if (value == null) {
                 throw new IllegalStateException("Value is null for key '" + key + "'. Template '" + template + "'. Keys: " + Join.join(", ", map.keySet()));
             }
-            matcher.appendReplacement(buf, value);
+            matcher.appendReplacement(buf, value.replace("$", "\\$")); // inner class have a $ we need to escape cause it means sthg for regex
         }
 
         matcher.appendTail(buf);