/*
* 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.runtime.module.deployment.internal;
import static org.apache.commons.lang.StringUtils.removeEndIgnoreCase;
import static org.mule.runtime.core.util.SplashScreen.miniSplash;
import static org.mule.runtime.module.reboot.MuleContainerBootstrapUtils.getMuleAppsDir;
import org.mule.runtime.api.i18n.I18nMessageFactory;
import org.mule.runtime.core.config.i18n.CoreMessages;
import org.mule.runtime.core.util.CollectionUtils;
import org.mule.runtime.deployment.model.api.DeployableArtifact;
import org.mule.runtime.deployment.model.api.DeploymentException;
import org.mule.runtime.module.artifact.Artifact;
import org.mule.runtime.module.deployment.api.DeploymentListener;
import org.mule.runtime.module.deployment.impl.internal.artifact.ArtifactFactory;
import org.mule.runtime.module.deployment.internal.util.ObservableList;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Deployer of an artifact within mule container. - Keeps track of deployed artifacts - Avoid already deployed artifacts to be
* redeployed - Deploys, undeploys, redeploys packaged and exploded artifacts
*/
public class DefaultArchiveDeployer<T extends DeployableArtifact> implements ArchiveDeployer<T> {
public static final String ARTIFACT_NAME_PROPERTY = "artifactName";
public static final String JAR_FILE_SUFFIX = ".jar";
private static final Logger logger = LoggerFactory.getLogger(DefaultArchiveDeployer.class);
private final ArtifactDeployer<T> deployer;
private final ArtifactArchiveInstaller artifactArchiveInstaller;
private final Map<String, ZombieFile> artifactZombieMap = new HashMap<String, ZombieFile>();
private final File artifactDir;
private final ObservableList<T> artifacts;
private final ArtifactDeploymentTemplate deploymentTemplate;
private ArtifactFactory<T> artifactFactory;
private DeploymentListener deploymentListener = new NullDeploymentListener();
public DefaultArchiveDeployer(final ArtifactDeployer deployer, final ArtifactFactory artifactFactory,
final ObservableList<T> artifacts,
ArtifactDeploymentTemplate deploymentTemplate) {
this.deployer = deployer;
this.artifactFactory = artifactFactory;
this.artifacts = artifacts;
this.deploymentTemplate = deploymentTemplate;
this.artifactDir = artifactFactory.getArtifactDir();
this.artifactArchiveInstaller = new ArtifactArchiveInstaller(artifactDir);
}
@Override
public T deployPackagedArtifact(String zip) throws DeploymentException {
URL url;
File artifactZip;
try {
final String artifactName = removeEndIgnoreCase(zip, JAR_FILE_SUFFIX);
artifactZip = new File(artifactDir, zip);
url = artifactZip.toURI().toURL();
return deployPackagedArtifact(url, artifactName);
} catch (DeploymentException e) {
throw e;
} catch (Exception e) {
throw new DeploymentException(CoreMessages.createStaticMessage("Failed to deploy from zip: " + zip), e);
}
}
@Override
public T deployExplodedArtifact(String artifactDir) throws DeploymentException {
if (!isUpdatedZombieArtifact(artifactDir)) {
return null;
}
return deployExplodedApp(artifactDir);
}
@Override
public boolean isUpdatedZombieArtifact(String artifactName) {
@SuppressWarnings("rawtypes")
Collection<String> deployedAppNames =
CollectionUtils.collect(artifacts, new BeanToPropertyValueTransformer(ARTIFACT_NAME_PROPERTY));
if (deployedAppNames.contains(artifactName) && (!artifactZombieMap.containsKey(artifactName))) {
return false;
}
ZombieFile zombieFile = artifactZombieMap.get(artifactName);
if ((zombieFile != null) && (!zombieFile.updatedZombieApp())) {
return false;
}
return true;
}
@Override
public void undeployArtifact(String artifactId) {
ZombieFile zombieFile = artifactZombieMap.get(artifactId);
if ((zombieFile != null)) {
if (zombieFile.exists()) {
return;
} else {
artifactZombieMap.remove(artifactId);
}
}
T artifact = (T) CollectionUtils.find(artifacts, new BeanPropertyValueEqualsPredicate(ARTIFACT_NAME_PROPERTY, artifactId));
undeploy(artifact);
}
@Override
public File getDeploymentDirectory() {
return artifactFactory.getArtifactDir();
}
@Override
public T deployPackagedArtifact(URL artifactAchivedUrl) throws DeploymentException {
T artifact;
try {
try {
artifact = installFrom(artifactAchivedUrl);
trackArtifact(artifact);
} catch (Throwable t) {
File artifactArchive = new File(artifactAchivedUrl.toURI());
String artifactName = removeEndIgnoreCase(artifactArchive.getName(), JAR_FILE_SUFFIX);
// error text has been created by the deployer already
logDeploymentFailure(t, artifactName);
addZombieFile(artifactName, artifactArchive);
deploymentListener.onDeploymentFailure(artifactName, t);
throw t;
}
deployArtifact(artifact);
return artifact;
} catch (Throwable t) {
if (t instanceof DeploymentException) {
// re-throw
throw ((DeploymentException) t);
}
final String msg = "Failed to deploy from URL: " + artifactAchivedUrl;
throw new DeploymentException(I18nMessageFactory.createStaticMessage(msg), t);
}
}
private void logDeploymentFailure(Throwable t, String artifactName) {
final String msg = miniSplash(String.format("Failed to deploy artifact '%s', see below", artifactName));
logger.error(msg, t);
}
@Override
public Map<URL, Long> getArtifactsZombieMap() {
Map<URL, Long> result = new HashMap<URL, Long>();
for (String artifact : artifactZombieMap.keySet()) {
ZombieFile file = artifactZombieMap.get(artifact);
result.put(file.url, file.originalTimestamp);
}
return result;
}
@Override
public void setArtifactFactory(final ArtifactFactory<T> artifactFactory) {
this.artifactFactory = artifactFactory;
}
@Override
public void undeployArtifactWithoutUninstall(T artifact) {
logRequestToUndeployArtifact(artifact);
try {
deploymentListener.onUndeploymentStart(artifact.getArtifactName());
deployer.undeploy(artifact);
deploymentListener.onUndeploymentSuccess(artifact.getArtifactName());
} catch (DeploymentException e) {
deploymentListener.onUndeploymentFailure(artifact.getArtifactName(), e);
throw e;
}
}
ArtifactDeployer getDeployer() {
return deployer;
}
@Override
public void setDeploymentListener(CompositeDeploymentListener deploymentListener) {
this.deploymentListener = deploymentListener;
}
private T deployPackagedArtifact(final URL artifactUrl, String artifactName) throws IOException {
ZombieFile zombieFile = artifactZombieMap.get(artifactName);
if (zombieFile != null) {
if (zombieFile.isFor(artifactUrl) && !zombieFile.updatedZombieApp()) {
// Skips the file because it was already deployed with failure
return null;
}
}
// check if this artifact is running first, undeployArtifact it then
T artifact = (T) CollectionUtils.find(artifacts, new BeanPropertyValueEqualsPredicate(ARTIFACT_NAME_PROPERTY, artifactName));
if (artifact != null) {
deploymentTemplate.preRedeploy(artifact);
undeployArtifact(artifactName);
}
T deployedAtifact = deployPackagedArtifact(artifactUrl);
deploymentTemplate.postRedeploy(deployedAtifact);
return deployedAtifact;
}
private T deployExplodedApp(String addedApp) throws DeploymentException {
if (logger.isInfoEnabled()) {
logger.info("================== New Exploded Artifact: " + addedApp);
}
T artifact;
try {
artifact = artifactFactory.createArtifact(new File(getMuleAppsDir(), addedApp));
// add to the list of known artifacts first to avoid deployment loop on failure
trackArtifact(artifact);
} catch (Throwable t) {
final File artifactDir1 = artifactDir;
File artifactDir = new File(artifactDir1, addedApp);
addZombieFile(addedApp, artifactDir);
String msg = miniSplash(String.format("Failed to deploy exploded artifact: '%s', see below", addedApp));
logger.error(msg, t);
deploymentListener.onDeploymentFailure(addedApp, t);
if (t instanceof DeploymentException) {
throw (DeploymentException) t;
} else {
msg = "Failed to deploy artifact: " + addedApp;
throw new DeploymentException(I18nMessageFactory.createStaticMessage(msg), t);
}
}
deployArtifact(artifact);
return artifact;
}
@Override
public void deployArtifact(T artifact) throws DeploymentException {
try {
// add to the list of known artifacts first to avoid deployment loop on failure
trackArtifact(artifact);
deploymentListener.onDeploymentStart(artifact.getArtifactName());
deployer.deploy(artifact);
artifactArchiveInstaller.createAnchorFile(artifact.getArtifactName());
deploymentListener.onDeploymentSuccess(artifact.getArtifactName());
artifactZombieMap.remove(artifact.getArtifactName());
} catch (Throwable t) {
// error text has been created by the deployer already
String msg = miniSplash(String.format("Failed to deploy artifact '%s', see below", artifact.getArtifactName()));
logger.error(msg, t);
addZombieApp(artifact);
deploymentListener.onDeploymentFailure(artifact.getArtifactName(), t);
if (t instanceof DeploymentException) {
throw (DeploymentException) t;
} else {
msg = "Failed to deploy artifact: " + artifact.getArtifactName();
throw new DeploymentException(I18nMessageFactory.createStaticMessage(msg), t);
}
}
}
private void addZombieApp(Artifact artifact) {
File resourceFile = artifact.getResourceFiles()[0];
if (resourceFile.exists()) {
try {
artifactZombieMap.put(artifact.getArtifactName(), new ZombieFile(resourceFile));
} catch (Exception e) {
// ignore resource
}
}
}
private void addZombieFile(String artifactName, File marker) {
// no sync required as deploy operations are single-threaded
if (marker == null) {
return;
}
if (!marker.exists()) {
return;
}
try {
artifactZombieMap.put(artifactName, new ZombieFile(marker));
} catch (Exception e) {
logger.debug(String.format("Failed to mark an exploded artifact [%s] as a zombie", marker.getName()), e);
}
}
private T findArtifact(String artifactName) {
return (T) CollectionUtils.find(artifacts, new BeanPropertyValueEqualsPredicate(ARTIFACT_NAME_PROPERTY, artifactName));
}
private void trackArtifact(T artifact) {
preTrackArtifact(artifact);
artifacts.add(artifact);
}
public void preTrackArtifact(T artifact) {
T previousArtifact = findArtifact(artifact.getArtifactName());
artifacts.remove(previousArtifact);
}
private void undeploy(T artifact) {
logRequestToUndeployArtifact(artifact);
try {
deploymentListener.onUndeploymentStart(artifact.getArtifactName());
artifacts.remove(artifact);
deployer.undeploy(artifact);
artifactArchiveInstaller.desinstallArtifact(artifact.getArtifactName());
deploymentListener.onUndeploymentSuccess(artifact.getArtifactName());
logArtifactUndeployed(artifact);
} catch (RuntimeException e) {
deploymentListener.onUndeploymentFailure(artifact.getArtifactName(), e);
throw e;
}
}
private void logRequestToUndeployArtifact(T artifact) {
if (logger.isInfoEnabled()) {
logger.info("================== Request to Undeploy Artifact: " + artifact.getArtifactName());
}
}
private void logArtifactUndeployed(T artifact) {
if (logger.isInfoEnabled()) {
logger.info(miniSplash(String.format("Undeployed artifact '%s'", artifact.getArtifactName())));
}
}
private T installFrom(URL url) throws IOException {
File artifactLocation = artifactArchiveInstaller.installArtifact(url);
return artifactFactory.createArtifact(artifactLocation);
}
@Override
public void redeploy(T artifact) throws DeploymentException {
if (logger.isInfoEnabled()) {
logger.info(miniSplash(String.format("Redeploying artifact '%s'", artifact.getArtifactName())));
}
deploymentListener.onUndeploymentStart(artifact.getArtifactName());
try {
deployer.undeploy(artifact);
deploymentListener.onUndeploymentSuccess(artifact.getArtifactName());
} catch (Throwable e) {
// TODO make the exception better
deploymentListener.onUndeploymentFailure(artifact.getArtifactName(), e);
}
deploymentListener.onDeploymentStart(artifact.getArtifactName());
try {
artifact = artifactFactory.createArtifact(artifact.getLocation());
trackArtifact(artifact);
deployer.deploy(artifact);
artifactArchiveInstaller.createAnchorFile(artifact.getArtifactName());
deploymentListener.onDeploymentSuccess(artifact.getArtifactName());
} catch (Throwable t) {
try {
logDeploymentFailure(t, artifact.getArtifactName());
addZombieApp(artifact);
if (t instanceof DeploymentException) {
throw (DeploymentException) t;
}
String msg = "Failed to deploy artifact: " + artifact.getArtifactName();
throw new DeploymentException(I18nMessageFactory.createStaticMessage(msg), t);
} finally {
deploymentListener.onDeploymentFailure(artifact.getArtifactName(), t);
}
}
artifactZombieMap.remove(artifact.getArtifactName());
}
private static class ZombieFile {
URL url;
Long originalTimestamp;
File file;
private ZombieFile(File file) {
this.file = file;
originalTimestamp = file.lastModified();
try {
url = file.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
public boolean isFor(URL url) {
return this.url.equals(url);
}
public boolean updatedZombieApp() {
return originalTimestamp != file.lastModified();
}
public boolean exists() {
return file.exists();
}
}
}