Java tutorial
/* * $Id$ * -------------------------------------------------------------------------------------- * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.module.launcher; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.mule.api.MuleContext; import org.mule.api.config.MuleProperties; import org.mule.api.registry.MuleRegistry; import org.mule.config.StartupContext; import org.mule.module.launcher.application.Application; import org.mule.module.launcher.application.MuleApplicationClassLoaderFactory; import org.mule.module.launcher.application.TestApplicationFactory; import org.mule.tck.junit4.AbstractMuleContextTestCase; import org.mule.tck.probe.PollingProber; import org.mule.tck.probe.Probe; import org.mule.tck.probe.Prober; import org.mule.tck.probe.file.FileDoesNotExists; import org.mule.util.CollectionUtils; import org.mule.util.FileUtils; import org.mule.util.StringUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.junit.Test; public class DeploymentServiceTestCase extends AbstractMuleContextTestCase { protected static final int DEPLOYMENT_TIMEOUT = 20000; protected static final String[] NONE = new String[0]; protected static final int ONE_HOUR_IN_MILLISECONDS = 3600000; protected File muleHome; protected File appsDir; protected MuleDeploymentService deploymentService; protected DeploymentListener deploymentListener; @Override protected void doSetUp() throws Exception { super.doSetUp(); // set up some mule home structure final String tmpDir = System.getProperty("java.io.tmpdir"); muleHome = new File(tmpDir, getClass().getSimpleName() + System.currentTimeMillis()); appsDir = new File(muleHome, "apps"); appsDir.mkdirs(); System.setProperty(MuleProperties.MULE_HOME_DIRECTORY_PROPERTY, muleHome.getCanonicalPath()); new File(muleHome, "lib/shared/default").mkdirs(); deploymentListener = mock(DeploymentListener.class); deploymentService = new MuleDeploymentService(new MulePluginClassLoaderManager()); deploymentService.addDeploymentListener(deploymentListener); } @Override protected void doTearDown() throws Exception { // comment out the deletion to analyze results after test is done FileUtils.deleteTree(muleHome); if (deploymentService != null) { deploymentService.stop(); } super.doTearDown(); // this is a complex classloader setup and we can't reproduce standalone Mule 100%, // so trick the next test method into thinking it's the first run, otherwise // app resets CCL ref to null and breaks the next test Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); } @Test public void testDeployZipOnStartup() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertAppsDir(NONE, new String[] { "dummy-app" }, true); // just assert no privileged entries were put in the registry final Application app = findApp("dummy-app", 1); final MuleRegistry registry = app.getMuleContext().getRegistry(); // mule-app.properties from the zip archive must have loaded properly assertEquals("mule-app.properties should have been loaded.", "someValue", registry.get("myCustomProp")); } @Test public void testUpdateAppViaZip() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertAppsDir(NONE, new String[] { "dummy-app" }, true); assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size()); reset(deploymentListener); addAppArchive(url); assertUndeploymentSuccess(deploymentListener, "dummy-app"); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size()); assertAppsDir(NONE, new String[] { "dummy-app" }, true); } @Test public void redeploysAppWhenConfigChanges() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertAppsDir(NONE, new String[] { "dummy-app" }, true); assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size()); reset(deploymentListener); File configFile = new File(appsDir + "/dummy-app", "mule-config.xml"); configFile.setLastModified(configFile.lastModified() + 1000); assertUndeploymentSuccess(deploymentListener, "dummy-app"); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size()); assertAppsDir(NONE, new String[] { "dummy-app" }, true); } @Test public void testBrokenAppArchiveWithoutArgument() throws Exception { doBrokenAppArchiveTest(); } @Test public void testBrokenAppArchiveAsArgument() throws Exception { Map<String, Object> startupOptions = new HashMap<String, Object>(); startupOptions.put("app", "broken-app"); StartupContext.get().setStartupOptions(startupOptions); doBrokenAppArchiveTest(); } public void doBrokenAppArchiveTest() throws Exception { final URL url = getClass().getResource("/broken-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentFailure(deploymentListener, "broken-app.zip"); reset(deploymentListener); // let the file system's write-behind cache commit the delete operation? Thread.sleep(1000); // zip stays intact, no app dir created assertAppsDir(new String[] { "broken-app.zip" }, NONE, true); // don't assert dir contents, we want to check internal deployer state next assertAppsDir(NONE, new String[] { "dummy-app" }, false); assertEquals("No apps should have been registered with Mule.", 0, deploymentService.getApplications().size()); final Map<URL, Long> zombieMap = deploymentService.getZombieMap(); assertEquals("Wrong number of zombie apps registered.", 1, zombieMap.size()); final Map.Entry<URL, Long> zombie = zombieMap.entrySet().iterator().next(); assertEquals("Wrong URL tagged as zombie.", "broken-app.zip", new File(zombie.getKey().getFile()).getName()); assertTrue("Invalid lastModified value for file URL.", zombie.getValue() != -1); // Checks that the invalid zip was not deployed again try { assertDeploymentFailure(deploymentListener, "broken-app.zip"); fail("Install was invoked again for the broken application file"); } catch (AssertionError expected) { } } @Test public void testBrokenAppName() throws Exception { final URL url = getClass().getResource("/empty-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url, "app with spaces.zip"); deploymentService.start(); assertDeploymentFailure(deploymentListener, "app with spaces.zip"); // zip stays intact, no app dir created assertAppsDir(new String[] { "app with spaces.zip" }, NONE, true); final Map<URL, Long> zombieMap = deploymentService.getZombieMap(); assertEquals("Wrong number of zombie apps registered.", 1, zombieMap.size()); final Map.Entry<URL, Long> zombie = zombieMap.entrySet().iterator().next(); // Spaces are converted to %20 is returned by java file api :/ String appName = URLDecoder.decode(new File(zombie.getKey().getFile()).getName(), "UTF-8"); assertEquals("Wrong URL tagged as zombie.", "app with spaces.zip", appName); assertTrue("Invalid lastModified value for file URL.", zombie.getValue() != -1); } @Test public void testDeployAppNameWithZipSuffix() throws Exception { final URL url = getClass().getResource("/empty-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url, "empty-app.zip.zip"); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "empty-app.zip"); reset(deploymentListener); assertAppsDir(NONE, new String[] { "empty-app.zip" }, true); assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size()); // Checks that the empty-app.zip folder is not processed as a zip file assertNoDeploymentInvoked(deploymentListener); } @Test public void testDeployAsArgumentStartupOrder() throws Exception { final URL url = getClass().getResource("/empty-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url, "1.zip"); addAppArchive(url, "2.zip"); addAppArchive(url, "3.zip"); Map<String, Object> startupOptions = new HashMap<String, Object>(); startupOptions.put("app", "3:1:2"); StartupContext.get().setStartupOptions(startupOptions); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "1"); assertDeploymentSuccess(deploymentListener, "2"); assertDeploymentSuccess(deploymentListener, "3"); assertAppsDir(NONE, new String[] { "1", "2", "3" }, true); // When apps are passed as -app app1:app2:app3 the startup order matters List<Application> applications = deploymentService.getApplications(); assertNotNull(applications); assertEquals(3, applications.size()); assertEquals("3", applications.get(0).getAppName()); assertEquals("1", applications.get(1).getAppName()); assertEquals("2", applications.get(2).getAppName()); } @Test public void testDeploysAppJustOnce() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); Map<String, Object> startupOptions = new HashMap<String, Object>(); startupOptions.put("app", "dummy-app:dummy-app:dummy-app"); StartupContext.get().setStartupOptions(startupOptions); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertAppsDir(NONE, new String[] { "dummy-app" }, true); List<Application> applications = deploymentService.getApplications(); assertEquals(1, applications.size()); } @Test public void testTracksAppConfigUpdateTime() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); File appFolder = new File(appsDir.getPath(), "dummy-app"); FileUtils.unzip(new File(url.toURI()), appFolder); // Sets a modification time in the future File configFile = new File(appFolder, "mule-config.xml"); configFile.setLastModified(System.currentTimeMillis() + ONE_HOUR_IN_MILLISECONDS); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); reset(deploymentListener); assertNoDeploymentInvoked(deploymentListener); } @Test public void receivesMuleContextDeploymentNotifications() throws Exception { // NOTE: need an integration test like this because DefaultMuleApplication // class cannot be unit tested. final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertMuleContextCreated(deploymentListener, "dummy-app"); assertMuleContextInitialized(deploymentListener, "dummy-app"); assertMuleContextConfigured(deploymentListener, "dummy-app"); } @Test public void undeploysStoppedApp() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); final Application app = findApp("dummy-app", 1); app.stop(); deploymentService.undeploy(app); } @Test public void undeploysApplicationRemovingAnchorFile() throws Exception { final URL url = getClass().getResource("/dummy-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "dummy-app"); assertTrue("Unable to remove anchor file", removeAnchorFile("dummy-app")); assertUndeploymentSuccess(deploymentListener, "dummy-app"); } @Test public void undeploysAppCompletelyEvenOnStoppingException() throws Exception { final URL url = getClass().getResource("/empty-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); TestApplicationFactory appFactory = new TestApplicationFactory(new MuleApplicationClassLoaderFactory()); appFactory.setFailOnStopApplication(true); deploymentService.setAppFactory(appFactory); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "empty-app"); assertTrue("Unable to remove anchor file", removeAnchorFile("empty-app")); assertUndeploymentSuccess(deploymentListener, "empty-app"); assertAppFolderIsDeleted("empty-app"); } @Test public void undeploysAppCompletelyEvenOnDisposingException() throws Exception { final URL url = getClass().getResource("/empty-app.zip"); assertNotNull("Test app file not found " + url, url); addAppArchive(url); TestApplicationFactory appFactory = new TestApplicationFactory(new MuleApplicationClassLoaderFactory()); appFactory.setFailOnDisposeApplication(true); deploymentService.setAppFactory(appFactory); deploymentService.start(); assertDeploymentSuccess(deploymentListener, "empty-app"); assertTrue("Unable to remove anchor file", removeAnchorFile("empty-app")); assertUndeploymentSuccess(deploymentListener, "empty-app"); assertAppFolderIsDeleted("empty-app"); } private void assertDeploymentSuccess(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onDeploymentSuccess(appName); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return "Failed to deploy application: " + appName; } }); } private void assertMuleContextCreated(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onMuleContextCreated(eq(appName), any(MuleContext.class)); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return String.format("Did not received notification '%s' for app '%s'", "onMuleContextCreated", appName); } }); } private void assertMuleContextInitialized(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onMuleContextInitialised(eq(appName), any(MuleContext.class)); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return String.format("Did not received notification '%s' for app '%s'", "onMuleContextInitialised", appName); } }); } private void assertMuleContextConfigured(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onMuleContextConfigured(eq(appName), any(MuleContext.class)); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return String.format("Did not received notification '%s' for app '%s'", "onMuleContextConfigured", appName); } }); } private void assertUndeploymentSuccess(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onUndeploymentSuccess(appName); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return "Failed to undeploy application: " + appName; } }); } private void assertDeploymentFailure(final DeploymentListener listener, final String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); prober.check(new Probe() { public boolean isSatisfied() { try { verify(listener, times(1)).onDeploymentFailure(eq(appName), any(Throwable.class)); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return "Application deployment was supposed to fail for: " + appName; } }); } private void assertNoDeploymentInvoked(final DeploymentListener deploymentListener) { //TODO(pablo.kraan): look for a better way to test this boolean invoked; Prober prober = new PollingProber(MuleDeploymentService.DEFAULT_CHANGES_CHECK_INTERVAL_MS * 2, 100); try { prober.check(new Probe() { public boolean isSatisfied() { try { verify(deploymentListener, times(1)).onDeploymentStart(any(String.class)); return true; } catch (AssertionError e) { return false; } } public String describeFailure() { return "No deployment has started"; } }); invoked = true; } catch (AssertionError e) { invoked = false; } assertFalse("A deployment was started", invoked); } /** * Find a deployed app, performing some basic assertions. */ private Application findApp(final String appName, int totalAppsExpected) { // list all apps to validate total count final List<Application> apps = deploymentService.getApplications(); assertNotNull(apps); assertEquals(totalAppsExpected, apps.size()); final Application app = deploymentService.findApplication(appName); assertNotNull(app); return app; } private void assertAppsDir(String[] expectedZips, String[] expectedApps, boolean performValidation) { final String[] actualZips = appsDir.list(MuleDeploymentService.ZIP_APPS_FILTER); if (performValidation) { assertArrayEquals("Invalid Mule application archives set", expectedZips, actualZips); } final String[] actualApps = appsDir.list(DirectoryFileFilter.DIRECTORY); if (performValidation) { assertTrue("Invalid Mule exploded applications set", CollectionUtils.isEqualCollection(Arrays.asList(expectedApps), Arrays.asList(actualApps))); } } /** * Copies a given app archive to the apps folder for deployment. */ private void addAppArchive(URL url) throws IOException { addAppArchive(url, null); } /** * Copies a given app archive with a given target name to the apps folder for deployment */ private void addAppArchive(URL url, String targetFile) throws IOException { // copy is not atomic, copy to a temp file and rename instead (rename is atomic) final String tempFileName = new File((targetFile == null ? url.getFile() : targetFile) + ".part").getName(); final File tempFile = new File(appsDir, tempFileName); FileUtils.copyURLToFile(url, tempFile); tempFile.renameTo(new File(StringUtils.removeEnd(tempFile.getAbsolutePath(), ".part"))); } /** * Removes a given application anchor file in order to start application undeployment * @param appName name of application to undeploy * @return true if anchor file was deleted, false otherwise */ private boolean removeAnchorFile(String appName) { String anchorFileName = appName + MuleDeploymentService.APP_ANCHOR_SUFFIX; File anchorFile = new File(appsDir, anchorFileName); return anchorFile.delete(); } private void assertAppFolderIsDeleted(String appName) { Prober prober = new PollingProber(DEPLOYMENT_TIMEOUT, 100); File appFolder = new File(appsDir, appName); prober.check(new FileDoesNotExists(appFolder)); } }