You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwebbeans.apache.org by ar...@apache.org on 2021/05/17 21:28:06 UTC

[openwebbeans-meecrowave] branch master updated: MEECROWAVE-290: Implement reloadCallback

This is an automated email from the ASF dual-hosted git repository.

arne pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwebbeans-meecrowave.git


The following commit(s) were added to refs/heads/master by this push:
     new 324800a  MEECROWAVE-290: Implement reloadCallback
324800a is described below

commit 324800acd339786a5c7fa708a73d4c410c291716
Author: arne <ar...@apache.org>
AuthorDate: Wed May 12 21:31:22 2021 +0200

    MEECROWAVE-290: Implement reloadCallback
---
 .../java/org/apache/meecrowave/Meecrowave.java     |  16 +-
 .../java/org/apache/meecrowave/runner/Cli.java     |   1 +
 .../meecrowave/tomcat/MeecrowaveContextConfig.java |   7 +-
 .../watching/ReloadOnChangeController.java         |  14 +-
 .../java/org/apache/meecrowave/MeecrowaveTest.java |  13 +
 .../apache/meecrowave/junit/MeecrowaveRule.java    |   2 +-
 meecrowave-maven-plugin/pom.xml                    |  13 +
 .../apache/meecrowave/maven/MeecrowaveRunMojo.java |   5 +-
 .../meecrowave/maven/MeecrowaveRunMojoTest.java    | 261 +++++++++++++++++----
 .../src/test/java/org/app/AdditionalEndpoint.java  |  36 +++
 10 files changed, 303 insertions(+), 65 deletions(-)

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 @@ public class Meecrowave implements AutoCloseable {
         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 @@ public class Meecrowave implements AutoCloseable {
     // 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 @@ public class Meecrowave implements AutoCloseable {
 
     // 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 @@ public class Meecrowave implements AutoCloseable {
             }
         };
 
-        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 @@ public class Meecrowave implements AutoCloseable {
 
     // 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 @@ public class Cli implements Runnable, AutoCloseable {
                                     .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.HashMap;
 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 @@ public class MeecrowaveContextConfig extends ContextConfig {
     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 @@ public class MeecrowaveContextConfig extends ContextConfig {
                 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.ArrayList;
 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 @@ public class ReloadOnChangeController implements AutoCloseable, Runnable {
     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 @@ public class ReloadOnChangeController implements AutoCloseable, Runnable {
     }
 
     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 @@ public class MeecrowaveTest {
         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 @@ public class MeecrowaveRule extends MeecrowaveRuleBase<MeecrowaveRule> {
     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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
     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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
                         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.plugin.testing.MojoRule;
 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;
+public class MeecrowaveRunMojoTest {
 
-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;
+	private static final int RETRY_COUNT = 1000;
+	private static final int RETRY_WAIT_PERIOD = 500;
 
-public class MeecrowaveRunMojoTest {
-    @Rule
+	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 @@ public class MeecrowaveRunMojoTest {
         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 @@ public class MeecrowaveRunMojoTest {
                     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 @@ public class MeecrowaveRunMojoTest {
                 }
             }
         };
+    }
+
+    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";
+    }
+}

AW: [openwebbeans-meecrowave] branch master updated: MEECROWAVE-290: Implement reloadCallback

Posted by Arne Limburg <ar...@openknowledge.de>.
Of course,

no problem. I'll add it this afternoon.


Cheers,

Arne


--

Arne Limburg - Enterprise Architekt




OPEN KNOWLEDGE GmbH
Poststraße 1, 26122 Oldenburg
Mobil: +49 151 - 108 22 942
Tel: +49 441 - 4082-154
Fax: +49 441 - 4082-111
arne.limburg@openknowledge.de
www.openknowledge.de <https://www.openknowledge.de/>

Registergericht: Amtsgericht Oldenburg, HRB 4670
Geschäftsführer: Lars Röwekamp, Jens Schumann

Treffen Sie uns auf kommenden Konferenzen und Workshops:

Zu unseren Events<https://www.openknowledge.de/event/>





________________________________
Von: Romain Manni-Bucau <rm...@gmail.com>
Gesendet: Dienstag, 18. Mai 2021 08:36
An: openwebbeans-dev
Betreff: Re: [openwebbeans-meecrowave] branch master updated: MEECROWAVE-290: Implement reloadCallback

Hi Arne,

Is it ok to restore backward compatibility on DeploymentMeta? It is part of
our API and don't think we should break it for this feature (another
constructor defaulting the callback to null works for me).

Romain Manni-Bucau
@rmannibucau <https://twitter.com/rmannibucau> |  Blog
<https://smex-ctp.trendmicro.com:443/wis/clicktime/v1/query?url=https%3a%2f%2frmannibucau.metawerx.net&umid=4124e5d3-6362-4f8d-a119-98a944d3e8ec&auth=ab2dbe9a65917e05515ec2a89459f3e450df8ff8-31016e007ce4bf13812a0fe9e4bb9bab704e5a10> | Old Blog
Index<https://smex-ctp.trendmicro.com/wis/clicktime/v1/query?url=https%3a%2f%2frmannibucau.metawerx.net&umid=4124e5d3-6362-4f8d-a119-98a944d3e8ec&auth=ab2dbe9a65917e05515ec2a89459f3e450df8ff8-31016e007ce4bf13812a0fe9e4bb9bab704e5a10>
smex-ctp.trendmicro.com
An opiniated IT blogging.


<https://smex-ctp.trendmicro.com:443/wis/clicktime/v1/query?url=http%3a%2f%2frmannibucau.wordpress.com&umid=4124e5d3-6362-4f8d-a119-98a944d3e8ec&auth=ab2dbe9a65917e05515ec2a89459f3e450df8ff8-037ed41df7d484716d5ed6ae721601140f382c09> | Github <https://smex-ctp.trendmicro.com:443/wis/clicktime/v1/query?url=https%3a%2f%2fgithub.com%2frmannibucau&umid=4124e5d3-6362-4f8d-a119-98a944d3e8ec&auth=ab2dbe9a65917e05515ec2a89459f3e450df8ff8-0ec7933454f2e8c44640873878f8f0c395781545> |
LinkedIn <https://www.linkedin.com/in/rmannibucau> | Book
<https://www.packtpub.com/application-development/java-ee-8-high-performance>


Le lun. 17 mai 2021 à 23:28, <ar...@apache.org> a écrit :

> This is an automated email from the ASF dual-hosted git repository.
>
> arne pushed a commit to branch master
> in repository
> https://gitbox.apache.org/repos/asf/openwebbeans-meecrowave.git
>
>
> The following commit(s) were added to refs/heads/master by this push:
>      new 324800a  MEECROWAVE-290: Implement reloadCallback
> 324800a is described below
>
> commit 324800acd339786a5c7fa708a73d4c410c291716
> Author: arne <ar...@apache.org>
> AuthorDate: Wed May 12 21:31:22 2021 +0200
>
>     MEECROWAVE-290: Implement reloadCallback
> ---
>  .../java/org/apache/meecrowave/Meecrowave.java     |  16 +-
>  .../java/org/apache/meecrowave/runner/Cli.java     |   1 +
>  .../meecrowave/tomcat/MeecrowaveContextConfig.java |   7 +-
>  .../watching/ReloadOnChangeController.java         |  14 +-
>  .../java/org/apache/meecrowave/MeecrowaveTest.java |  13 +
>  .../apache/meecrowave/junit/MeecrowaveRule.java    |   2 +-
>  meecrowave-maven-plugin/pom.xml                    |  13 +
>  .../apache/meecrowave/maven/MeecrowaveRunMojo.java |   5 +-
>  .../meecrowave/maven/MeecrowaveRunMojoTest.java    | 261
> +++++++++++++++++----
>  .../src/test/java/org/app/AdditionalEndpoint.java  |  36 +++
>  10 files changed, 303 insertions(+), 65 deletions(-)
>
> 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 @@ public class Meecrowave implements AutoCloseable {
>          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 @@ public class Meecrowave implements AutoCloseable {
>      // 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 @@ public class Meecrowave implements AutoCloseable {
>
>      // 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 @@ public class Meecrowave implements AutoCloseable {
>              }
>          };
>
> -        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 @@ public class Meecrowave implements AutoCloseable {
>
>      // 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 @@ public class Cli implements Runnable, AutoCloseable {
>                                      .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.HashMap;
>  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 @@ public class MeecrowaveContextConfig extends
> ContextConfig {
>      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 @@ public class MeecrowaveContextConfig extends
> ContextConfig {
>                  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.ArrayList;
>  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 @@ public class ReloadOnChangeController implements
> AutoCloseable, Runnable {
>      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 @@ public class ReloadOnChangeController implements
> AutoCloseable, Runnable {
>      }
>
>      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 @@ public class MeecrowaveTest {
>          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 @@ public class MeecrowaveRule extends
> MeecrowaveRuleBase<MeecrowaveRule> {
>      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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
>      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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
>                          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.plugin.testing.MojoRule;
>  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;
> +public class MeecrowaveRunMojoTest {
>
> -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;
> +       private static final int RETRY_COUNT = 1000;
> +       private static final int RETRY_WAIT_PERIOD = 500;
>
> -public class MeecrowaveRunMojoTest {
> -    @Rule
> +       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 @@ public class MeecrowaveRunMojoTest {
>          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 @@ public class MeecrowaveRunMojoTest {
>                      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 @@ public class MeecrowaveRunMojoTest {
>                  }
>              }
>          };
> +    }
> +
> +    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";
> +    }
> +}
>

Re: [openwebbeans-meecrowave] branch master updated: MEECROWAVE-290: Implement reloadCallback

Posted by Romain Manni-Bucau <rm...@gmail.com>.
Hi Arne,

Is it ok to restore backward compatibility on DeploymentMeta? It is part of
our API and don't think we should break it for this feature (another
constructor defaulting the callback to null works for me).

Romain Manni-Bucau
@rmannibucau <https://twitter.com/rmannibucau> |  Blog
<https://rmannibucau.metawerx.net/> | Old Blog
<http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> |
LinkedIn <https://www.linkedin.com/in/rmannibucau> | Book
<https://www.packtpub.com/application-development/java-ee-8-high-performance>


Le lun. 17 mai 2021 à 23:28, <ar...@apache.org> a écrit :

> This is an automated email from the ASF dual-hosted git repository.
>
> arne pushed a commit to branch master
> in repository
> https://gitbox.apache.org/repos/asf/openwebbeans-meecrowave.git
>
>
> The following commit(s) were added to refs/heads/master by this push:
>      new 324800a  MEECROWAVE-290: Implement reloadCallback
> 324800a is described below
>
> commit 324800acd339786a5c7fa708a73d4c410c291716
> Author: arne <ar...@apache.org>
> AuthorDate: Wed May 12 21:31:22 2021 +0200
>
>     MEECROWAVE-290: Implement reloadCallback
> ---
>  .../java/org/apache/meecrowave/Meecrowave.java     |  16 +-
>  .../java/org/apache/meecrowave/runner/Cli.java     |   1 +
>  .../meecrowave/tomcat/MeecrowaveContextConfig.java |   7 +-
>  .../watching/ReloadOnChangeController.java         |  14 +-
>  .../java/org/apache/meecrowave/MeecrowaveTest.java |  13 +
>  .../apache/meecrowave/junit/MeecrowaveRule.java    |   2 +-
>  meecrowave-maven-plugin/pom.xml                    |  13 +
>  .../apache/meecrowave/maven/MeecrowaveRunMojo.java |   5 +-
>  .../meecrowave/maven/MeecrowaveRunMojoTest.java    | 261
> +++++++++++++++++----
>  .../src/test/java/org/app/AdditionalEndpoint.java  |  36 +++
>  10 files changed, 303 insertions(+), 65 deletions(-)
>
> 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 @@ public class Meecrowave implements AutoCloseable {
>          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 @@ public class Meecrowave implements AutoCloseable {
>      // 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 @@ public class Meecrowave implements AutoCloseable {
>
>      // 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 @@ public class Meecrowave implements AutoCloseable {
>              }
>          };
>
> -        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 @@ public class Meecrowave implements AutoCloseable {
>
>      // 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 @@ public class Cli implements Runnable, AutoCloseable {
>                                      .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.HashMap;
>  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 @@ public class MeecrowaveContextConfig extends
> ContextConfig {
>      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 @@ public class MeecrowaveContextConfig extends
> ContextConfig {
>                  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.ArrayList;
>  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 @@ public class ReloadOnChangeController implements
> AutoCloseable, Runnable {
>      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 @@ public class ReloadOnChangeController implements
> AutoCloseable, Runnable {
>      }
>
>      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 @@ public class MeecrowaveTest {
>          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 @@ public class MeecrowaveRule extends
> MeecrowaveRuleBase<MeecrowaveRule> {
>      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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
>      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 @@ public class MeecrowaveRunMojo extends AbstractMojo {
>                          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.plugin.testing.MojoRule;
>  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;
> +public class MeecrowaveRunMojoTest {
>
> -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;
> +       private static final int RETRY_COUNT = 1000;
> +       private static final int RETRY_WAIT_PERIOD = 500;
>
> -public class MeecrowaveRunMojoTest {
> -    @Rule
> +       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 @@ public class MeecrowaveRunMojoTest {
>          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 @@ public class MeecrowaveRunMojoTest {
>                      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 @@ public class MeecrowaveRunMojoTest {
>                  }
>              }
>          };
> +    }
> +
> +    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";
> +    }
> +}
>