org.mule.module.launcher.DeploymentDirectoryWatcher Maven / Gradle / Ivy
/*
* 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.mule.module.launcher.DefaultArchiveDeployer.ARTIFACT_NAME_PROPERTY;
import static org.mule.module.launcher.DefaultArchiveDeployer.ZIP_FILE_SUFFIX;
import static org.mule.util.SplashScreen.miniSplash;
import org.mule.config.StartupContext;
import org.mule.module.launcher.application.Application;
import org.mule.module.launcher.artifact.Artifact;
import org.mule.module.launcher.domain.Domain;
import org.mule.module.launcher.util.DebuggableReentrantLock;
import org.mule.module.launcher.util.ElementAddedEvent;
import org.mule.module.launcher.util.ElementRemovedEvent;
import org.mule.module.launcher.util.ObservableList;
import org.mule.util.ArrayUtils;
import org.mule.util.CollectionUtils;
import org.mule.util.StringUtils;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
import org.apache.commons.collections.Predicate;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* It's in charge of the whole deployment process.
*
* It will deploy the applications at the container startup process.
* It will periodically scan the artifact directories in order to process new deployments,
* remove artifacts that were previously deployed but the anchor file was removed and redeploy
* those applications which configuration has changed.
*/
public class DeploymentDirectoryWatcher implements Runnable
{
public static final String ARTIFACT_ANCHOR_SUFFIX = "-anchor.txt";
public static final String CHANGE_CHECK_INTERVAL_PROPERTY = "mule.launcher.changeCheckInterval";
public static final IOFileFilter ZIP_ARTIFACT_FILTER = new AndFileFilter(new SuffixFileFilter(ZIP_FILE_SUFFIX), FileFileFilter.FILE);
protected static final int DEFAULT_CHANGES_CHECK_INTERVAL_MS = 5000;
protected transient final Log logger = LogFactory.getLog(getClass());
private final ReentrantLock deploymentLock;
private final ArchiveDeployer domainArchiveDeployer;
private final ArchiveDeployer applicationArchiveDeployer;
private final ArtifactTimestampListener applicationTimestampListener;
private final ArtifactTimestampListener domainTimestampListener;
private final ObservableList applications;
private final ObservableList domains;
private final File appsDir;
private final File domainsDir;
private ScheduledExecutorService artifactDirMonitorTimer;
protected volatile boolean dirty;
public DeploymentDirectoryWatcher(final ArchiveDeployer domainArchiveDeployer, final ArchiveDeployer applicationArchiveDeployer, ObservableList domains, ObservableList applications, final ReentrantLock deploymentLock)
{
this.appsDir = applicationArchiveDeployer.getDeploymentDirectory();
this.domainsDir = domainArchiveDeployer.getDeploymentDirectory();
this.deploymentLock = deploymentLock;
this.domainArchiveDeployer = domainArchiveDeployer;
this.applicationArchiveDeployer = applicationArchiveDeployer;
this.applications = applications;
this.domains = domains;
applications.addPropertyChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
if (e instanceof ElementAddedEvent || e instanceof ElementRemovedEvent)
{
if (logger.isDebugEnabled())
{
logger.debug("Deployed applications set has been modified, flushing state.");
}
dirty = true;
}
}
});
domains.addPropertyChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
if (e instanceof ElementAddedEvent || e instanceof ElementRemovedEvent)
{
if (logger.isDebugEnabled())
{
logger.debug("Deployed applications set has been modified, flushing state.");
}
dirty = true;
}
}
});
this.applicationTimestampListener = new ArtifactTimestampListener(applications);
this.domainTimestampListener = new ArtifactTimestampListener(domains);
}
/**
* Starts the process of deployment / undeployment of artifact.
*
* It wil schedule a task for periodically scan the deployment directories.
*/
public void start()
{
deploymentLock.lock();
deleteAllAnchors();
// mule -app app1:app2:app3 will restrict deployment only to those specified apps
final Map options = StartupContext.get().getStartupOptions();
String appString = (String) options.get("app");
try
{
String[] explodedDomains = domainsDir.list(DirectoryFileFilter.DIRECTORY);
String[] packagedDomains = domainsDir.list(ZIP_ARTIFACT_FILTER);
deployPackedDomains(packagedDomains);
deployExplodedDomains(explodedDomains);
if (appString == null)
{
String[] explodedApps = appsDir.list(DirectoryFileFilter.DIRECTORY);
String[] packagedApps = appsDir.list(ZIP_ARTIFACT_FILTER);
deployPackedApps(packagedApps);
deployExplodedApps(explodedApps);
}
else
{
String[] apps = appString.split(":");
apps = removeDuplicateAppNames(apps);
for (String app : apps)
{
try
{
File applicationFile = new File(appsDir, app + ZIP_FILE_SUFFIX);
if (applicationFile.exists() && applicationFile.isFile())
{
applicationArchiveDeployer.deployPackagedArtifact(app + ZIP_FILE_SUFFIX);
}
else
{
applicationArchiveDeployer.deployExplodedArtifact(app);
}
}
catch (Exception e)
{
// Ignore and continue
}
}
}
}
finally
{
if (deploymentLock.isHeldByCurrentThread())
{
deploymentLock.unlock();
}
}
// only start the monitor thread if we launched in default mode without explicitly
// stated applications to launch
if (!(appString != null))
{
scheduleChangeMonitor();
}
else
{
if (logger.isInfoEnabled())
{
logger.info(miniSplash("Mule is up and running in a fixed app set mode"));
}
}
}
/**
* Stops the deployment scan service.
*/
public void stop()
{
stopAppDirMonitorTimer();
deploymentLock.lock();
try
{
stopArtifacts(applications);
stopArtifacts(domains);
}
finally
{
deploymentLock.unlock();
}
}
private void stopArtifacts(List extends Artifact> artifacts)
{
Collections.reverse(artifacts);
for (Artifact artifact : artifacts)
{
try
{
artifact.stop();
artifact.dispose();
}
catch (Throwable t)
{
logger.error(t);
}
}
}
private static int getChangesCheckIntervalMs()
{
try
{
String value = System.getProperty(CHANGE_CHECK_INTERVAL_PROPERTY);
return Integer.parseInt(value);
}
catch (NumberFormatException e)
{
return DEFAULT_CHANGES_CHECK_INTERVAL_MS;
}
}
private void scheduleChangeMonitor()
{
final int reloadIntervalMs = getChangesCheckIntervalMs();
artifactDirMonitorTimer = Executors.newSingleThreadScheduledExecutor(new ArtifactDeployerMonitorThreadFactory());
artifactDirMonitorTimer.scheduleWithFixedDelay(this,
0,
reloadIntervalMs,
TimeUnit.MILLISECONDS);
if (logger.isInfoEnabled())
{
logger.info(miniSplash(String.format("Mule is up and kicking (every %dms)", reloadIntervalMs)));
}
}
private void deployPackedApps(String[] zips)
{
for (String zip : zips)
{
try
{
applicationArchiveDeployer.deployPackagedArtifact(zip);
}
catch (Exception e)
{
// Ignore and continue
}
}
}
private void deployExplodedApps(String[] apps)
{
for (String addedApp : apps)
{
try
{
applicationArchiveDeployer.deployExplodedArtifact(addedApp);
}
catch (DeploymentException e)
{
// Ignore and continue
}
}
}
// Cycle is:
// undeployArtifact removed apps
// undeployArtifact removed domains
// deploy domain archives
// deploy domain exploded
// redeploy modified apps
// deploy archives apps
// deploy exploded apps
public void run()
{
try
{
if (logger.isDebugEnabled())
{
logger.debug("Checking for changes...");
}
// use non-barging lock to preserve fairness, according to javadocs
// if there's a lock present - wait for next poll to do anything
if (!deploymentLock.tryLock(0, TimeUnit.SECONDS))
{
if (logger.isDebugEnabled())
{
logger.debug("Another deployment operation in progress, will skip this cycle. Owner thread: " +
((DebuggableReentrantLock) deploymentLock).getOwner());
}
return;
}
undeployRemovedApps();
undeployRemovedDomains();
// list new apps
String[] domains = domainsDir.list(DirectoryFileFilter.DIRECTORY);
final String[] domainZips = domainsDir.list(ZIP_ARTIFACT_FILTER);
redeployModifiedDomains();
deployPackedDomains(domainZips);
// re-scan exploded domains and update our state, as deploying Mule domains archives might have added some
if (domainZips.length > 0 || dirty)
{
domains = domainsDir.list(DirectoryFileFilter.DIRECTORY);
}
deployExplodedDomains(domains);
redeployModifiedApplications();
// list new apps
String[] apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
final String[] appZips = appsDir.list(ZIP_ARTIFACT_FILTER);
deployPackedApps(appZips);
// re-scan exploded apps and update our state, as deploying Mule app archives might have added some
if (appZips.length > 0 || dirty)
{
apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
}
deployExplodedApps(apps);
}
catch (Exception e)
{
// preserve the flag for the thread
Thread.currentThread().interrupt();
}
finally
{
if (deploymentLock.isHeldByCurrentThread())
{
deploymentLock.unlock();
}
dirty = false;
}
}
public T findArtifact(String artifactName, ObservableList artifacts)
{
return (T) CollectionUtils.find(artifacts, new BeanPropertyValueEqualsPredicate(ARTIFACT_NAME_PROPERTY, artifactName));
}
private void undeployRemovedDomains()
{
undeployRemovedArtifacts(domainsDir, domains, domainArchiveDeployer);
}
private void undeployRemovedApps()
{
undeployRemovedArtifacts(appsDir, applications, applicationArchiveDeployer);
}
private void undeployRemovedArtifacts(File artifactDir, ObservableList extends Artifact> artifacts, ArchiveDeployer extends Artifact> archiveDeployer)
{
// we care only about removed anchors
String[] currentAnchors = artifactDir.list(new SuffixFileFilter(ARTIFACT_ANCHOR_SUFFIX));
if (logger.isDebugEnabled())
{
StringBuilder sb = new StringBuilder();
sb.append(String.format("Current anchors:%n"));
for (String currentAnchor : currentAnchors)
{
sb.append(String.format(" %s%n", currentAnchor));
}
logger.debug(sb.toString());
}
String[] artifactAnchors = findExpectedAnchorFiles(artifacts);
@SuppressWarnings("unchecked")
final Collection deletedAnchors = CollectionUtils.subtract(Arrays.asList(artifactAnchors), Arrays.asList(currentAnchors));
if (logger.isDebugEnabled())
{
StringBuilder sb = new StringBuilder();
sb.append(String.format("Deleted anchors:%n"));
for (String deletedAnchor : deletedAnchors)
{
sb.append(String.format(" %s%n", deletedAnchor));
}
logger.debug(sb.toString());
}
for (String deletedAnchor : deletedAnchors)
{
String artifactName = StringUtils.removeEnd(deletedAnchor, ARTIFACT_ANCHOR_SUFFIX);
try
{
if (findArtifact(artifactName, artifacts) != null)
{
archiveDeployer.undeployArtifact(artifactName);
}
else if (logger.isDebugEnabled())
{
logger.debug(String.format("Artifact [%s] has already been undeployed via API", artifactName));
}
}
catch (Throwable t)
{
logger.error("Failed to undeployArtifact artifact: " + artifactName, t);
}
}
}
/**
* Returns the list of anchor file names for the deployed apps
*
* @return a non null list of file names
*/
private String[] findExpectedAnchorFiles(ObservableList extends Artifact> artifacts)
{
String[] anchors = new String[artifacts.size()];
int i = 0;
for (Artifact artifact : artifacts)
{
anchors[i++] = artifact.getArtifactName() + ARTIFACT_ANCHOR_SUFFIX;
}
return anchors;
}
private void deployExplodedDomains(String[] domains)
{
for (String addedApp : domains)
{
try
{
domainArchiveDeployer.deployExplodedArtifact(addedApp);
}
catch (DeploymentException e)
{
// Ignore and continue
}
}
}
private void deployPackedDomains(String[] zips)
{
for (String zip : zips)
{
try
{
domainArchiveDeployer.deployPackagedArtifact(zip);
}
catch (Exception e)
{
// Ignore and continue
}
}
}
private void deleteAllAnchors()
{
deleteAnchorsFromDirectory(domainsDir);
deleteAnchorsFromDirectory(appsDir);
}
private void deleteAnchorsFromDirectory(final File directory)
{
// Deletes any leftover anchor files from previous shutdowns
String[] anchors = directory.list(new SuffixFileFilter(ARTIFACT_ANCHOR_SUFFIX));
for (String anchor : anchors)
{
// ignore result
new File(directory, anchor).delete();
}
}
private String[] removeDuplicateAppNames(String[] apps)
{
List appNames = new LinkedList();
for (String appName : apps)
{
if (!appNames.contains(appName))
{
appNames.add(appName);
}
}
return appNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
private void redeployModifiedDomains()
{
Collection redeployableDomains = getArtifactsToRedeploy(domains);
redeployModifiedArtifacts(redeployableDomains, domainTimestampListener, domainArchiveDeployer);
}
private void redeployModifiedApplications()
{
Collection redeployableApplications = getArtifactsToRedeploy(applications);
redeployModifiedArtifacts(redeployableApplications, applicationTimestampListener, applicationArchiveDeployer);
}
private Collection getArtifactsToRedeploy(Collection collection)
{
return CollectionUtils.select(collection, new Predicate()
{
@Override
public boolean evaluate(Object object)
{
return ((Artifact) object).getDescriptor().isRedeploymentEnabled();
}
});
}
private void redeployModifiedArtifacts(Collection artifacts, ArtifactTimestampListener artifactTimestampListener, ArchiveDeployer artifactArchiveDeployer)
{
for (T artifact : artifacts)
{
if (artifactTimestampListener.isArtifactResourceUpdated(artifact))
{
try
{
artifactArchiveDeployer.redeploy(artifact);
}
catch (DeploymentException e)
{
if (logger.isDebugEnabled())
{
logger.debug(e);
}
}
}
}
}
private void stopAppDirMonitorTimer()
{
if (artifactDirMonitorTimer != null)
{
artifactDirMonitorTimer.shutdown();
try
{
artifactDirMonitorTimer.awaitTermination(getChangesCheckIntervalMs(), TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
private static class ArtifactTimestampListener implements PropertyChangeListener
{
private Map> artifactConfigResourcesTimestaps = new HashMap>();
public ArtifactTimestampListener(ObservableList artifacts)
{
artifacts.addPropertyChangeListener(this);
}
@Override
public void propertyChange(PropertyChangeEvent event)
{
if (event instanceof ElementAddedEvent)
{
Artifact artifactAdded = (T) event.getNewValue();
artifactConfigResourcesTimestaps.put(artifactAdded.getArtifactName(), new ArtifactResourcesTimestamp(artifactAdded));
}
else if (event instanceof ElementRemovedEvent)
{
Artifact artifactRemoved = (T) event.getNewValue();
artifactConfigResourcesTimestaps.remove(artifactRemoved.getArtifactName());
}
}
public boolean isArtifactResourceUpdated(T artifact)
{
ArtifactResourcesTimestamp applicationResourcesTimestamp = artifactConfigResourcesTimestaps.get(artifact.getArtifactName());
return !applicationResourcesTimestamp.resourcesHaveSameTimestamp(artifact);
}
}
private static class ArtifactResourcesTimestamp
{
private final Map timestampsPerResource = new HashMap();
public ArtifactResourcesTimestamp(final Artifact artifact)
{
for (File configResourceFile : artifact.getResourceFiles())
{
timestampsPerResource.put(configResourceFile.getAbsolutePath(), configResourceFile.lastModified());
}
}
public boolean resourcesHaveSameTimestamp(final T artifact)
{
boolean resourcesHaveSameTimestamp = true;
for (File configResourceFile : artifact.getResourceFiles())
{
long originalTimestamp = timestampsPerResource.get(configResourceFile.getAbsolutePath());
long currentTimestamp = configResourceFile.lastModified();
if (originalTimestamp != currentTimestamp)
{
timestampsPerResource.put(configResourceFile.getAbsolutePath(), currentTimestamp);
resourcesHaveSameTimestamp = false;
}
}
return resourcesHaveSameTimestamp;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy