/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.maven.plugins; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.SystemStreamLog; import org.apache.openejb.config.DeploymentFilterable; import org.apache.openejb.loader.IO; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.util.NetworkUtil; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.net.URL; import java.util.HashMap; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static java.lang.System.lineSeparator; import static java.lang.Thread.sleep; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class TomEEEmbeddedMojoTest { @Before // there can be a small latency stopping/restarting tomcat so ensure we don't have conflicts between tests public void ensureTomcatIsDown() throws MalformedObjectNameException, IntrospectionException, ReflectionException { for (int i = 0; i < 10; i++) { try { assertFalse(SystemInstance.isInitialized()); try { assertNull(ManagementFactory.getPlatformMBeanServer().getMBeanInfo(new ObjectName("Tomcat:type=Server"))); } catch (final InstanceNotFoundException e) { // ok } } catch (final AssertionError ae) { try { sleep(1000); } catch (final InterruptedException e) { Thread.interrupted(); fail(); } } } } @Test public void run() throws MojoFailureException, MojoExecutionException, IOException, InterruptedException { final File docBase = new File("target/TomEEEmbeddedMojoTest/base"); docBase.mkdirs(); try (final FileWriter w = new FileWriter(new File(docBase, "index.html"))) { w.write("initial"); } // we use a dynamic InputStream to be able to simulate commands without hacking System.in final Input input = new Input(); final Semaphore reloaded = new Semaphore(0); final CountDownLatch started = new CountDownLatch(1); final TomEEEmbeddedMojo mojo = new TomEEEmbeddedMojo() { @Override protected Scanner newScanner() { return new Scanner(input); } }; mojo.classpathAsWar = true; mojo.httpPort = NetworkUtil.getNextAvailablePort(); mojo.ssl = false; mojo.docBase = docBase; mojo.forceReloadable = true; mojo.webResourceCached = false; mojo.containerProperties = new HashMap<>(); mojo.containerProperties.put(DeploymentFilterable.CLASSPATH_INCLUDE, ".*tomee-embedded-maven-plugin.*"); mojo.containerProperties.put("openejb.additional.include", "tomee-embedded-maven-plugin"); mojo.setLog(new SystemStreamLog() { // not the best solution but fine for now... @Override public void info(final CharSequence charSequence) { final String string = charSequence.toString(); if (string.startsWith("TomEE embedded started on") || string.equals("can't start TomEE")) { started.countDown(); } else if (string.contains("Redeployed /")) { reloaded.release(); } super.info(charSequence); } }); final CountDownLatch stopped = doStart(started, mojo); assertEquals("ok", IO.slurp(new URL("http://localhost:" + mojo.httpPort + "/endpoint/")).trim()); long initTs = timestamp(mojo); assertEquals("initial", IO.slurp(new URL("http://localhost:" + mojo.httpPort + "/")).trim()); try (final FileWriter w = new FileWriter(new File(docBase, "index.html"))) { w.write("changed"); } assertEquals("changed", IO.slurp(new URL("http://localhost:" + mojo.httpPort + "/")).trim()); assertEquals(timestamp(mojo), initTs); for (int i = 0; i < 4; i++) { // ensure it works multiple times System.out.println("Reloading, #" + (i + 1)); try { // ensure timestamp changed even on super fast machines sleep(200); } catch (final InterruptedException e) { Thread.interrupted(); fail(); } input.write("reload"); reloaded.tryAcquire(5, TimeUnit.MINUTES); final long newTimestamp = timestamp(mojo); assertTrue(Integer.toString(i) + " iteration", newTimestamp > initTs); initTs = newTimestamp; try { // check timestamp is fixed and we didn't code wrong the test sleep(200); } catch (final InterruptedException e) { Thread.interrupted(); fail(); } assertEquals(timestamp(mojo), initTs); } input.write("exit"); stopped.await(5, TimeUnit.MINUTES); input.close(); } @Test public void customWebResource() throws Exception { final File docBase = new File("target/TomEEEmbeddedMojoTest/customWebResource"); docBase.mkdirs(); try (final FileWriter w = new FileWriter(new File(docBase, "index.html"))) { w.write("resource"); } // we use a dynamic InputStream to be able to simulate commands without hacking System.in final Input input = new Input(); final Semaphore reloaded = new Semaphore(0); final CountDownLatch started = new CountDownLatch(1); final TomEEEmbeddedMojo mojo = new TomEEEmbeddedMojo() { @Override protected Scanner newScanner() { return new Scanner(input); } }; mojo.classpathAsWar = true; mojo.httpPort = NetworkUtil.getNextAvailablePort(); mojo.ssl = false; mojo.webResources = singletonList(docBase); mojo.webResourceCached = false; mojo.setLog(new SystemStreamLog() { // not the best solution but fine for now... @Override public void info(final CharSequence charSequence) { final String string = charSequence.toString(); if (string.startsWith("TomEE embedded started on") || string.equals("can't start TomEE")) { started.countDown(); } else if (string.contains("Redeployed /")) { reloaded.release(); } super.info(charSequence); } }); final CountDownLatch stopped = doStart(started, mojo); assertEquals("resource", IO.slurp(new URL("http://localhost:" + mojo.httpPort + "/")).trim()); input.write("exit"); stopped.await(5, TimeUnit.MINUTES); input.close(); } @Test public void customScript() throws Exception { Assume.assumeFalse(System.getProperty("java.version").startsWith("1.7")); // we use a dynamic InputStream to be able to simulate commands without hacking System.in final Input input = new Input(); final Semaphore reloaded = new Semaphore(0); final CountDownLatch started = new CountDownLatch(1); final TomEEEmbeddedMojo mojo = new TomEEEmbeddedMojo() { @Override protected Scanner newScanner() { return new Scanner(input); } }; mojo.classpathAsWar = true; mojo.httpPort = NetworkUtil.getNextAvailablePort(); mojo.ssl = false; mojo.webResourceCached = false; mojo.jsCustomizers = singletonList( "var File = Java.type('java.io.File');" + "var FileWriter = Java.type('java.io.FileWriter');" + "var out = new File(catalinaBase, 'conf/app.conf');" + "var writer = new FileWriter(out);" + "writer.write('test=ok');" + "writer.close();"); mojo.setLog(new SystemStreamLog() { // not the best solution but fine for now... @Override public void info(final CharSequence charSequence) { final String string = charSequence.toString(); if (string.startsWith("TomEE embedded started on") || string.equals("can't start TomEE")) { started.countDown(); } else if (string.contains("Redeployed /")) { reloaded.release(); } super.info(charSequence); } }); CountDownLatch stopped = null; try { stopped = doStart(started, mojo); final File appConf = new File(System.getProperty("catalina.base"), "conf/app.conf"); assertTrue(appConf.exists()); assertEquals("ok", IO.readProperties(appConf).getProperty("test", "ko")); } finally { input.write("exit"); if (stopped != null) { stopped.await(5, TimeUnit.MINUTES); } input.close(); } } private CountDownLatch doStart(final CountDownLatch started, final TomEEEmbeddedMojo mojo) { final CountDownLatch stopped = new CountDownLatch(1); final AtomicReference<Exception> error = new AtomicReference<>(); final Thread mojoThread = new Thread() { { setName("Mojo-Starter"); } @Override public void run() { try { mojo.execute(); } catch (final Exception e) { error.set(e); } finally { stopped.countDown(); } } }; mojoThread.start(); try { started.await(10, TimeUnit.MINUTES); } catch (final InterruptedException e) { Thread.interrupted(); } assertNull("all started fine", error.get()); return stopped; } private long timestamp(final TomEEEmbeddedMojo mojo) throws IOException { return Long.parseLong(IO.slurp(new URL("http://localhost:" + mojo.httpPort + "/endpoint/timestamp")).trim()); } private static final class Input extends InputStream { private final Semaphore inputSema = new Semaphore(0); private InputStream currentInput = null; private boolean metEnd = true; private void write(final String data) { try { currentInput = new ByteArrayInputStream((data + lineSeparator()).getBytes("UTF-8")); } catch (final UnsupportedEncodingException e) { currentInput = new ByteArrayInputStream((data + lineSeparator()).getBytes()); } inputSema.release(); } @Override public int read() throws IOException { int read; if (currentInput == null || (read = currentInput.read()) < 0) { if (!metEnd) { // ensure scanner gets an end event metEnd = true; currentInput = null; return -1; } try { inputSema.acquire(1); } catch (final InterruptedException e) { Thread.interrupted(); fail(); } read = currentInput != null ? currentInput.read() : -1; if (read > 0) { metEnd = false; } } return read; } @Override public void close() throws IOException { inputSema.release(); super.close(); } } }