All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mule.module.launcher.DefaultArchiveDeployer Maven / Gradle / Ivy

There is a newer version: 3.9.0
Show newest version
/*
 * 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.util.SplashScreen.miniSplash;

import org.mule.config.i18n.CoreMessages;
import org.mule.config.i18n.MessageFactory;
import org.mule.module.launcher.application.NullDeploymentListener;
import org.mule.module.launcher.artifact.Artifact;
import org.mule.module.launcher.artifact.ArtifactFactory;
import org.mule.module.launcher.util.ObservableList;
import org.mule.util.CollectionUtils;
import org.mule.util.StringUtils;

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 java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 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 implements ArchiveDeployer
{

    public static final String ARTIFACT_NAME_PROPERTY = "artifactName";
    public static final String ZIP_FILE_SUFFIX = ".zip";
    public static final String ANOTHER_DEPLOYMENT_OPERATION_IS_IN_PROGRESS = "Another deployment operation is in progress";
    public static final String INSTALL_OPERATION_HAS_BEEN_INTERRUPTED = "Install operation has been interrupted";
    private static final Log logger = LogFactory.getLog(DefaultArchiveDeployer.class);

    private final ArtifactDeployer deployer;
    private final ArtifactArchiveInstaller artifactArchiveInstaller;
    private final ReentrantLock deploymentLock;
    private final Map artifactZombieMap = new HashMap();
    private final File artifactDir;
    private final ObservableList artifacts;
    private final ArtifactDeploymentTemplate deploymentTemplate;
    private ArtifactFactory artifactFactory;
    private DeploymentListener deploymentListener = new NullDeploymentListener();


    public DefaultArchiveDeployer(final ArtifactDeployer deployer, final ArtifactFactory artifactFactory, final ObservableList artifacts, final ReentrantLock lock,
            ArtifactDeploymentTemplate deploymentTemplate)
    {
        this.deployer = deployer;
        this.artifactFactory = artifactFactory;
        this.artifacts = artifacts;
        this.deploymentLock = lock;
        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 = StringUtils.removeEnd(zip, ZIP_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
    {
        String artifactName = artifactDir;
        @SuppressWarnings("rawtypes")
        Collection deployedAppNames = CollectionUtils.collect(artifacts, new BeanToPropertyValueTransformer(ARTIFACT_NAME_PROPERTY));

        if (deployedAppNames.contains(artifactName) && (!artifactZombieMap.containsKey(artifactName)))
        {
            return null;
        }

        ZombieFile zombieFile = artifactZombieMap.get(artifactName);

        if ((zombieFile != null) && (!zombieFile.updatedZombieApp()))
        {
            return null;
        }

        return deployExplodedApp(artifactName);
    }

    @Override
    public void undeployArtifact(String artifactId)
    {
        ZombieFile zombieFile = artifactZombieMap.get(artifactId);
        if ((zombieFile != null))
        {
            return;
        }

        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 = guardedInstallFrom(artifactAchivedUrl);
                trackArtifact(artifact);
            }
            catch (Throwable t)
            {
                File artifactArchive = new File(artifactAchivedUrl.toURI());
                String artifactName = StringUtils.removeEnd(artifactArchive.getName(), ZIP_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(MessageFactory.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 getArtifactsZombieMap()
    {
        Map result = new HashMap();

        for (String artifact : artifactZombieMap.keySet())
        {
            ZombieFile file = artifactZombieMap.get(artifact);
            result.put(file.url, file.originalTimestamp);
        }
        return result;
    }

    @Override
    public void setArtifactFactory(final ArtifactFactory artifactFactory)
    {
        this.artifactFactory = artifactFactory;
    }

    @Override
    public void undeployArtifactWithoutUninstall(T artifact)
    {
        logRequestToUndeployArtifact(artifact);
        try
        {
            if (!deploymentLock.tryLock(0, TimeUnit.SECONDS))
            {
                return;
            }

            deploymentListener.onUndeploymentStart(artifact.getArtifactName());
            deployer.undeploy(artifact);
            deploymentListener.onUndeploymentSuccess(artifact.getArtifactName());
        }
        catch (DeploymentException e)
        {
            deploymentListener.onUndeploymentFailure(artifact.getArtifactName(), e);
            throw e;
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        finally
        {
            if (deploymentLock.isHeldByCurrentThread())
            {
                deploymentLock.unlock();
            }
        }
    }

    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(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(MessageFactory.createStaticMessage(msg), t);
            }
        }

        deployArtifact(artifact);
        return artifact;
    }


    private void guardedDeploy(T artifact)
    {
        try
        {
            if (!deploymentLock.tryLock(0, TimeUnit.SECONDS))
            {
                return;
            }
            deployer.deploy(artifact);
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        finally
        {
            if (deploymentLock.isHeldByCurrentThread())
            {
                deploymentLock.unlock();
            }
        }
    }

    @Override
    public void deployArtifact(T artifact) throws DeploymentException
    {
        try
        {
            deploymentListener.onDeploymentStart(artifact.getArtifactName());
            guardedDeploy(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(MessageFactory.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);
            guardedUndeploy(artifact);

            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 guardedInstallFrom(URL artifactUrl) throws IOException
    {
        try
        {
            if (!deploymentLock.tryLock(0, TimeUnit.SECONDS))
            {
                throw new IOException(ANOTHER_DEPLOYMENT_OPERATION_IS_IN_PROGRESS);
            }
            return installFrom(artifactUrl);
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
            throw new IOException(INSTALL_OPERATION_HAS_BEEN_INTERRUPTED);
        }
        finally
        {
            if (deploymentLock.isHeldByCurrentThread())
            {
                deploymentLock.unlock();
            }
        }
    }

    private T installFrom(URL url) throws IOException
    {
        String artifactName = artifactArchiveInstaller.installArtifact(url);
        return artifactFactory.createArtifact(artifactName);
    }

    private void guardedUndeploy(T artifact)
    {
        try
        {
            if (!deploymentLock.tryLock(0, TimeUnit.SECONDS))
            {
                return;
            }

            deployer.undeploy(artifact);
            artifactArchiveInstaller.desinstallArtifact(artifact.getArtifactName());
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        finally
        {
            if (deploymentLock.isHeldByCurrentThread())
            {
                deploymentLock.unlock();
            }
        }
    }

    @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
        {
            deployer.deploy(artifact);
            artifactArchiveInstaller.createAnchorFile(artifact.getArtifactName());
            deploymentListener.onDeploymentSuccess(artifact.getArtifactName());
        }
        catch (Throwable t)
        {
            try
            {
                logDeploymentFailure(t, artifact.getArtifactName());
                if (t instanceof DeploymentException)
                {
                    throw (DeploymentException) t;
                }
                String msg = "Failed to deploy artifact: " + artifact.getArtifactName();
                throw new DeploymentException(MessageFactory.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();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy