MEECROWAVE-290: Implement reloadCallback
diff --git a/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java b/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
index 705567f..4bb064d 100644
--- a/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
+++ b/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
@@ -190,7 +190,7 @@
         return deployWebapp(new DeploymentMeta(meta.context, meta.docBase, ofNullable(meta.consumer).map(c -> (Consumer<Context>) ctx -> {
             builtInCustomizer.accept(ctx);
             c.accept(ctx);
-        }).orElse(builtInCustomizer)));
+        }).orElse(builtInCustomizer), meta.redeployCallback));
     }
 
     // shortcut
@@ -201,12 +201,12 @@
     // shortcut
     public Meecrowave bake(final Consumer<Context> customizer) {
         start();
-        return deployClasspath(new DeploymentMeta("", null, customizer));
+        return deployClasspath(new DeploymentMeta("", null, customizer, null));
     }
 
     // shortcut (used by plugins)
     public Meecrowave deployClasspath(final String context) {
-        return deployClasspath(new DeploymentMeta(context, null, null));
+        return deployClasspath(new DeploymentMeta(context, null, null, null));
     }
 
     // shortcut
@@ -216,7 +216,7 @@
 
     // shortcut (used by plugins)
     public Meecrowave deployWebapp(final String context, final File warOrDir) {
-        return deployWebapp(new DeploymentMeta(context, warOrDir, null));
+        return deployWebapp(new DeploymentMeta(context, warOrDir, null, null));
     }
 
     public Meecrowave deployWebapp(final DeploymentMeta meta) {
@@ -320,7 +320,7 @@
             }
         };
 
-        ctx.addLifecycleListener(new MeecrowaveContextConfig(configuration, meta.docBase != null, meecrowaveInitializer));
+        ctx.addLifecycleListener(new MeecrowaveContextConfig(configuration, meta.docBase != null, meecrowaveInitializer, meta.redeployCallback));
         ctx.addLifecycleListener(event -> {
             switch (event.getType()) {
                 case Lifecycle.BEFORE_START_EVENT:
@@ -1973,14 +1973,16 @@
 
     // there to be able to stack config later on without breaking all methods
     public static class DeploymentMeta {
-        private final String context;
+		private final String context;
         private final File docBase;
         private final Consumer<Context> consumer;
+        private final Consumer<Context> redeployCallback;
 
-        public DeploymentMeta(final String context, final File docBase, final Consumer<Context> consumer) {
+        public DeploymentMeta(final String context, final File docBase, final Consumer<Context> consumer, final Consumer<Context> redeployCallback) {
             this.context = context;
             this.docBase = docBase;
             this.consumer = consumer;
+            this.redeployCallback = redeployCallback;
         }
     }
 
diff --git a/meecrowave-core/src/main/java/org/apache/meecrowave/runner/Cli.java b/meecrowave-core/src/main/java/org/apache/meecrowave/runner/Cli.java
index e5545a7..220b6ac 100644
--- a/meecrowave-core/src/main/java/org/apache/meecrowave/runner/Cli.java
+++ b/meecrowave-core/src/main/java/org/apache/meecrowave/runner/Cli.java
@@ -91,6 +91,7 @@
                                     .filter(File::isDirectory)
                                     .findFirst()
                                     .orElse(null)),
+                        null,
                         null));
             } else {
                 meecrowave.deployWebapp(fixedCtx, new File(war));
diff --git a/meecrowave-core/src/main/java/org/apache/meecrowave/tomcat/MeecrowaveContextConfig.java b/meecrowave-core/src/main/java/org/apache/meecrowave/tomcat/MeecrowaveContextConfig.java
index 261da27..1bd5124 100644
--- a/meecrowave-core/src/main/java/org/apache/meecrowave/tomcat/MeecrowaveContextConfig.java
+++ b/meecrowave-core/src/main/java/org/apache/meecrowave/tomcat/MeecrowaveContextConfig.java
@@ -34,6 +34,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 import javax.servlet.ServletContainerInitializer;
@@ -68,13 +69,15 @@
     private final Map<String, Collection<Class<?>>> webClasses = new HashMap<>();
     private final boolean fixDocBase;
     private final ServletContainerInitializer intializer;
+	private final Consumer<Context> redeployCallback;
     private OwbAnnotationFinder finder;
     private ReloadOnChangeController watcher;
 
-    public MeecrowaveContextConfig(final Configuration configuration, final boolean fixDocBase, final ServletContainerInitializer intializer) {
+    public MeecrowaveContextConfig(final Configuration configuration, final boolean fixDocBase, final ServletContainerInitializer intializer, final Consumer<Context> redeployCallback) {
         this.configuration = configuration;
         this.fixDocBase = fixDocBase;
         this.intializer= intializer;
+        this.redeployCallback = redeployCallback;
     }
 
     @Override
@@ -111,7 +114,7 @@
                 scannerService.setDocBase(context.getDocBase());
                 scannerService.setShared(configuration.getSharedLibraries());
                 if (configuration.getWatcherBouncing() > 0) { // note that caching should be disabled with this config in most of the times
-                    watcher = new ReloadOnChangeController(context, configuration.getWatcherBouncing());
+                    watcher = new ReloadOnChangeController(context, configuration.getWatcherBouncing(), redeployCallback);
                     scannerService.setFileVisitor(f -> watcher.register(f));
                 }
                 scannerService.scan();
diff --git a/meecrowave-core/src/main/java/org/apache/meecrowave/watching/ReloadOnChangeController.java b/meecrowave-core/src/main/java/org/apache/meecrowave/watching/ReloadOnChangeController.java
index 0a34601..a75ed04 100644
--- a/meecrowave-core/src/main/java/org/apache/meecrowave/watching/ReloadOnChangeController.java
+++ b/meecrowave-core/src/main/java/org/apache/meecrowave/watching/ReloadOnChangeController.java
@@ -18,8 +18,8 @@
  */
 package org.apache.meecrowave.watching;
 
-import org.apache.catalina.Context;
-import org.apache.meecrowave.logging.tomcat.LogFacade;
+import static java.util.Arrays.asList;
+import static java.util.Optional.ofNullable;
 
 import java.io.File;
 import java.io.IOException;
@@ -36,12 +36,15 @@
 import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
-import static java.util.Arrays.asList;
+import org.apache.catalina.Context;
+import org.apache.meecrowave.logging.tomcat.LogFacade;
 
 public class ReloadOnChangeController implements AutoCloseable, Runnable {
     private final Context context;
     private final long bouncing;
+    private final Consumer<Context> redeployCallback;
     private final Collection<Path> paths = new ArrayList<>();
     private WatchService watchService;
     private Thread bouncer;
@@ -49,9 +52,10 @@
     private volatile boolean running = true;
     private volatile long redeployMarker = System.nanoTime();
 
-    public ReloadOnChangeController(final Context context, final int watcherBouncing) {
+    public ReloadOnChangeController(final Context context, final int watcherBouncing, final Consumer<Context> redeployCallback) {
         this.context = context;
         this.bouncing = (long) watcherBouncing;
+        this.redeployCallback = ofNullable(redeployCallback).orElse(Context::reload);
     }
 
     public void register(final File folder) {
@@ -79,7 +83,7 @@
     }
 
     protected synchronized void redeploy() {
-        context.reload();
+        redeployCallback.accept(context);
     }
 
     @Override
diff --git a/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java b/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
index f7cd553..e5a2caa 100644
--- a/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
+++ b/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
@@ -215,6 +215,19 @@
         assertEquals(
                 "sci:" + Bounced.class.getName() + Endpoint.class.getName() + InterfaceApi.class.getName() + RsApp.class.getName() + TestJsonEndpoint.class.getName(),
                 slurp(new URL("http://localhost:" + meecrowave.getConfiguration().getHttpPort() + "/sci")));
+        assertNotAvailable(new URL("http://localhost:" + meecrowave.getConfiguration().getHttpPort() + "/api/other"));
+        assertNotAvailable(new URL("http://localhost:" + meecrowave.getConfiguration().getHttpPort() + "/other"));
+    }
+
+    private void assertNotAvailable(final URL url) {
+    	try {
+    		URLConnection connection = url.openConnection();
+    		connection.setReadTimeout(500);
+			connection.getInputStream();
+			fail(url.toString() + " is available");
+		} catch (Exception e) {
+			assertTrue(e.getMessage(), e instanceof IOException);
+		}
     }
 
     private String slurp(final URL url) {
diff --git a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit/MeecrowaveRule.java b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit/MeecrowaveRule.java
index ba30e23..40a47b1 100644
--- a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit/MeecrowaveRule.java
+++ b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit/MeecrowaveRule.java
@@ -58,7 +58,7 @@
     protected AutoCloseable onStart() {
         final Meecrowave meecrowave = new Meecrowave(configuration);
         meecrowave.start();
-        meecrowave.deployClasspath(new Meecrowave.DeploymentMeta(context, docBase, customizer));
+        meecrowave.deployClasspath(new Meecrowave.DeploymentMeta(context, docBase, customizer, null));
         return meecrowave;
     }
 }
diff --git a/meecrowave-maven-plugin/pom.xml b/meecrowave-maven-plugin/pom.xml
index 6d99bf1..bb5b1a2 100644
--- a/meecrowave-maven-plugin/pom.xml
+++ b/meecrowave-maven-plugin/pom.xml
@@ -34,6 +34,19 @@
     <meecrowave.build.name>${project.groupId}.maven</meecrowave.build.name>
   </properties>
 
+  <profiles>
+    <profile>
+      <id>dev</id> <!-- IDE does not see that it is a shade so workaround it with an IDE profile -->
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.meecrowave</groupId>
+          <artifactId>meecrowave-specs-api</artifactId>
+          <version>${project.version}</version>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
+
   <dependencies>
     <dependency>
       <groupId>org.apache.maven</groupId>
diff --git a/meecrowave-maven-plugin/src/main/java/org/apache/meecrowave/maven/MeecrowaveRunMojo.java b/meecrowave-maven-plugin/src/main/java/org/apache/meecrowave/maven/MeecrowaveRunMojo.java
index 378c356..7f2bd04 100644
--- a/meecrowave-maven-plugin/src/main/java/org/apache/meecrowave/maven/MeecrowaveRunMojo.java
+++ b/meecrowave-maven-plugin/src/main/java/org/apache/meecrowave/maven/MeecrowaveRunMojo.java
@@ -309,7 +309,7 @@
     private boolean jaxwsSupportIfAvailable;
 
     @Parameter(property = "meecrowave.reload-goals")
-    private List<String> reloadGoals; // todo: add watching on project.build.directory?
+    private List<String> reloadGoals;
 
     @Parameter(property = "meecrowave.default-ssl-hostconfig-name")
     private String defaultSSLHostConfigName;
@@ -367,7 +367,8 @@
                         webapp != null && webapp.isDirectory() ? webapp : null,
                         jsContextCustomizer == null ?
                                 null : ctx -> scriptCustomization(
-                                singletonList(jsContextCustomizer), "js", singletonMap("context", ctx)));
+                                singletonList(jsContextCustomizer), "js", singletonMap("context", ctx)),
+                                context -> reload(meecrowave, fixedContext, appLoaderSupplier, loader));
                 deploy(meecrowave, deploymentMeta);
                 final Scanner scanner = new Scanner(System.in);
                 String cmd;
diff --git a/meecrowave-maven-plugin/src/test/java/org/apache/meecrowave/maven/MeecrowaveRunMojoTest.java b/meecrowave-maven-plugin/src/test/java/org/apache/meecrowave/maven/MeecrowaveRunMojoTest.java
index 84b2dc0..f487538 100644
--- a/meecrowave-maven-plugin/src/test/java/org/apache/meecrowave/maven/MeecrowaveRunMojoTest.java
+++ b/meecrowave-maven-plugin/src/test/java/org/apache/meecrowave/maven/MeecrowaveRunMojoTest.java
@@ -18,7 +18,34 @@
  */
 package org.apache.meecrowave.maven;
 
+import static java.util.Optional.ofNullable;
+import static org.apache.ziplock.JarLocation.jarLocation;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import javax.enterprise.inject.Model;
+
 import org.apache.commons.io.IOUtils;
+import org.apache.cxf.helpers.FileUtils;
 import org.apache.maven.execution.DefaultMavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenSession;
@@ -27,34 +54,54 @@
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuilder;
 import org.apache.maven.project.ProjectBuildingRequest;
-import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.apache.meecrowave.io.IO;
+import org.app.Endpoint;
+import org.app.Injectable;
+import org.app.RsApp;
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
 import org.eclipse.aether.repository.LocalRepository;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.ServerSocket;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.apache.ziplock.JarLocation.jarLocation;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 public class MeecrowaveRunMojoTest {
-    @Rule
+
+	private static final int RETRY_COUNT = 1000;
+	private static final int RETRY_WAIT_PERIOD = 500;
+
+	private static byte[] additionalEndpointClass;
+
+	@Rule
     public final MojoRule mojo = new MojoRule();
 
-    @Test
-    public void run() throws Exception {
+	private MavenProject project;
+    private MavenSession session;
+    private int port;
+    private MojoExecution execution;
+
+    @BeforeClass
+    public static void removeAdditionalEndpointClass() throws Exception {
+        File additionalEndpointClassFile = getAdditionalEndpointClass();
+        try (InputStream classStream = new FileInputStream(additionalEndpointClassFile)) {
+            additionalEndpointClass = IOUtils.toByteArray(classStream);
+        }
+        assumeTrue(additionalEndpointClassFile.delete());
+    }
+
+    @AfterClass
+    public static void restoreAdditionalEndpointClass() throws Exception {
+        IOUtils.write(additionalEndpointClass, new FileOutputStream(getAdditionalEndpointClass()));
+    }
+
+    private static File getAdditionalEndpointClass() throws URISyntaxException {
+        return new File(new File(MeecrowaveRunMojoTest.class.getResource("/").toURI()), "org/app/AdditionalEndpoint.class");
+    }
+
+    @Before
+	public void setupMojoExecution() throws Exception {
         final File moduleBase = jarLocation(MeecrowaveRunMojoTest.class).getParentFile().getParentFile();
         final File basedir = new File(moduleBase, "src/test/resources/" + getClass().getSimpleName());
         final File pom = new File(basedir, "pom.xml");
@@ -65,16 +112,120 @@
         repositorySession.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory()
                 .newInstance(repositorySession, new LocalRepository(new File(moduleBase, "target/fake"), "")));
         configuration.setRepositorySession(repositorySession);
-        final MavenProject project = mojo.lookup(ProjectBuilder.class).build(pom, configuration).getProject();
-        final MavenSession session = mojo.newMavenSession(project);
-        final int port;
+        project = mojo.lookup(ProjectBuilder.class).build(pom, configuration).getProject();
+        session = mojo.newMavenSession(project);
         try (final ServerSocket serverSocket = new ServerSocket(0)) {
             port = serverSocket.getLocalPort();
         }
-        final MojoExecution execution = mojo.newMojoExecution("run");
-        execution.getConfiguration().addChild(new Xpp3Dom("httpPort") {{
-            setValue(Integer.toString(port));
-        }});
+        execution = mojo.newMojoExecution("run");
+	}
+
+	@Test
+    public void classpathDeployment() throws Exception {
+        execution.getConfiguration().getChild("httpPort").setValue(Integer.toString(port));
+        final Runnable quitCommand = quitCommand();
+        final Thread mojoExecutor = mojoExecutor();
+        try {
+            mojoExecutor.start();
+            retry(() -> {
+            	assertEquals("simple", IOUtils.toString(new URL("http://localhost:" + port + "/api/test")));
+            	assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("first_name"));
+            	assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("last_name"));
+            	assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("firstname"));
+            	assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("null"));
+            	assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/sub/index.html")).contains("<h1>yes</h1>"));
+            	assertNotAvailable(new URL("http://localhost:" + port + "/api/additional"));
+            	quitCommand.run();
+            });
+        } finally {
+            mojoExecutor.join(TimeUnit.MINUTES.toMillis(1));
+            if (mojoExecutor.isAlive()) {
+                mojoExecutor.interrupt();
+                fail("Runner didn't terminate properly");
+            }
+        }
+    }
+
+    @Test
+    public void webappDeployment() throws Exception {
+        File target = new File(MeecrowaveRunMojoTest.class.getResource("/").toURI()).getParentFile();
+        File webappDirectory = new File(target, MeecrowaveRunMojoTest.class.getSimpleName());
+        webappDirectory.mkdir();
+        assertTrue(webappDirectory.exists());
+        setupWebapp(webappDirectory);
+        execution.getConfiguration().getChild("httpPort").setValue(Integer.toString(port));
+        execution.getConfiguration().getChild("useClasspathDeployment").setValue("false");
+        execution.getConfiguration().getChild("webapp").setValue(webappDirectory.getAbsolutePath());
+        final Runnable quitCommand = quitCommand();
+        final Thread mojoExecutor = mojoExecutor();
+        try {
+            mojoExecutor.start();
+            retry(() -> {
+                assertEquals("simple", IOUtils.toString(new URL("http://localhost:" + port + "/api/test")));
+                assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("first_name"));
+                assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("last_name"));
+                assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("firstname"));
+                assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("null"));
+                assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/additional")).contains("available"));
+                assertNotAvailable(new URL("http://localhost:" + port + "/sub/index.html"));
+                quitCommand.run();
+            });
+        } finally {
+            mojoExecutor.join(TimeUnit.MINUTES.toMillis(1));
+            if (mojoExecutor.isAlive()) {
+                mojoExecutor.interrupt();
+                fail("Runner didn't terminate properly");
+            }
+        }
+    }
+
+    @Test
+    public void autoreloadWithClasspathDeployment() throws Exception {
+        File additionalEndpointFile = getAdditionalEndpointClass();
+        execution.getConfiguration().getChild("httpPort").setValue(Integer.toString(port));
+        execution.getConfiguration().getChild("watcherBouncing").setValue("1");
+        Runnable quitCommand = quitCommand();
+        final Thread mojoExecutor = mojoExecutor();
+        try {
+            mojoExecutor.start();
+            retry(() -> {
+                assertEquals("simple", IOUtils.toString(new URL("http://localhost:" + port + "/api/test")));
+                assertNotAvailable(new URL("http://localhost:" + port + "/api/additional"));
+            });
+            File folder = additionalEndpointFile.getParentFile();
+            folder.mkdirs();
+            assertTrue(folder.exists());
+            IOUtils.write(additionalEndpointClass, new FileOutputStream(additionalEndpointFile));
+            retry(() -> assertEquals("available", IOUtils.toString(new URL("http://localhost:" + port + "/api/additional"))));
+			retry(() -> assertEquals("simple", IOUtils.toString(new URL("http://localhost:" + port + "/api/test"))));
+            quitCommand.run();
+        } finally {
+            additionalEndpointFile.delete();
+            assertFalse(additionalEndpointFile.exists());
+            mojoExecutor.join(TimeUnit.MINUTES.toMillis(1));
+            if (mojoExecutor.isAlive()) {
+                mojoExecutor.interrupt();
+                fail("Runner didn't terminate properly");
+            }
+        }
+    }
+
+    private void setupWebapp(File webappDirectory) throws Exception {
+        Stream.of(Endpoint.class, RsApp.class, Injectable.class, Model.class).forEach(type -> {
+            final String target = type.getName().replace(".", "/");
+            File targetFile = new File(webappDirectory, "WEB-INF/classes/" + target + ".class");
+            FileUtils.mkDir(targetFile.getParentFile());
+            try (final InputStream from = Thread.currentThread().getContextClassLoader().getResourceAsStream(target + ".class");
+                 final OutputStream to = new FileOutputStream(targetFile)) {
+                IO.copy(from, to);
+            } catch (final IOException e) {
+                fail(e.getMessage());
+            }
+        });
+        IOUtils.write(additionalEndpointClass, new FileOutputStream(new File(webappDirectory, "WEB-INF/classes/org/app/AdditionalEndpoint.class")));
+    }
+
+    private Runnable quitCommand() {
         final InputStream in = System.in;
         final CountDownLatch latch = new CountDownLatch(1);
         System.setIn(new InputStream() {
@@ -88,10 +239,19 @@
                     Thread.currentThread().interrupt();
                     fail(e.getMessage());
                 }
-                return delegate.read();
+                if (delegate.available() > 0) {
+                	return delegate.read();
+                } else {
+                	System.setIn(in);
+                	return -1;
+                }
             }
         });
-        final Thread runner = new Thread() {
+    	return latch::countDown;
+    }
+
+    private Thread mojoExecutor() {
+        return new Thread() {
             @Override
             public void run() {
                 try {
@@ -101,29 +261,34 @@
                 }
             }
         };
+    }
+
+    private void assertNotAvailable(final URL url) {
         try {
-            runner.start();
-            for (int i = 0; i < 120; i++) {
-                try {
-                    assertEquals("simple", IOUtils.toString(new URL("http://localhost:" + port + "/api/test")));
-                    assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("first_name"));
-                    assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("last_name"));
-                    assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("firstname"));
-                    assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/api/test/model")).contains("null"));
-                    assertTrue(IOUtils.toString(new URL("http://localhost:" + port + "/sub/index.html")).contains("<h1>yes</h1>"));
-                    latch.countDown();
-                    break;
-                } catch (final Exception | AssertionError e) {
-                    Thread.sleep(500);
-                }
-            }
-        } finally {
-            runner.join(TimeUnit.MINUTES.toMillis(1));
-            System.setIn(in);
-            if (runner.isAlive()) {
-                runner.interrupt();
-                fail("Runner didn't terminate properly");
+            URLConnection connection = url.openConnection();
+            connection.setReadTimeout(500);
+            connection.getInputStream();
+            fail(url.toString() + " is available");
+        } catch (Exception e) {
+            assertTrue(e.getMessage(), e instanceof IOException);
+        }
+    }
+
+    private void retry(RetryTemplate retryTemplate) throws InterruptedException {
+        Throwable error = null;
+        for (int i = 0; i < RETRY_COUNT; i++) {
+            try {
+                retryTemplate.retry();
+                return;
+            } catch (Exception | AssertionError e) {
+                error = e;
+                Thread.sleep(RETRY_WAIT_PERIOD);
             }
         }
+        fail(ofNullable(error).map(Throwable::getMessage).orElse("retry failes"));
+    }
+
+    interface RetryTemplate {
+    	void retry() throws Exception;
     }
 }
diff --git a/meecrowave-maven-plugin/src/test/java/org/app/AdditionalEndpoint.java b/meecrowave-maven-plugin/src/test/java/org/app/AdditionalEndpoint.java
new file mode 100644
index 0000000..54c524b
--- /dev/null
+++ b/meecrowave-maven-plugin/src/test/java/org/app/AdditionalEndpoint.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.app;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("additional")
+@ApplicationScoped
+public class AdditionalEndpoint {
+
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String available() {
+       return "available";
+    }
+}