Package org.jboss.as.server.standalone.deployment

Source Code of org.jboss.as.server.standalone.deployment.FileSystemDeploymentService

/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.as.server.standalone.deployment;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.jboss.as.deployment.scanner.DeploymentScanner;
import org.jboss.as.model.ServerGroupDeploymentElement;
import org.jboss.as.model.ServerModel;
import org.jboss.as.standalone.client.api.deployment.DeploymentAction;
import org.jboss.as.standalone.client.api.deployment.DeploymentPlan;
import org.jboss.as.standalone.client.api.deployment.DeploymentPlanBuilder;
import org.jboss.as.standalone.client.api.deployment.DuplicateDeploymentNameException;
import org.jboss.as.standalone.client.api.deployment.ServerDeploymentManager;
import org.jboss.as.standalone.client.api.deployment.ServerDeploymentPlanResult;
import org.jboss.logging.Logger;

/**
* Service that monitors the filesystem for deployment content and if found
* deploys it.
*
* @author Brian Stansberry
*/
class FileSystemDeploymentService implements DeploymentScanner {
    // FIXME get this list from elsewhere
    private static final Set<String> ARCHIVES = new HashSet<String>(Arrays.asList(".jar", ".war", ".ear", ".rar", ".sar", ".beans"));
    private static final Logger log = Logger.getLogger("org.jboss.as.deployment");
    private static final String DEPLOYED = ".deployed";

    private File deploymentDir;
    private long scanInterval = 0;
    private volatile boolean scanEnabled = false;
    private ScheduledFuture<?> scanTask;
    private final Lock scanLock = new ReentrantLock();
    private Set<String> deployed = new HashSet<String>();

    private ServerModel serverModel;
    private ServerDeploymentManager deploymentManager;
    private ScheduledExecutorService scheduledExecutor;

    //TODO Extenalize filter config
    private FileFilter filter = new ExtensibleFilter();

    FileSystemDeploymentService(final File deploymentDir, final long scanInterval) {
        this.deploymentDir = deploymentDir;
        this.scanInterval = scanInterval;
    }

    /** {@inheritDoc} */
    public boolean isEnabled() {
        return false;
    }

    public long getScanInterval() {
        return scanInterval;
    }

    public synchronized void setScanInterval(long scanInterval) {
        if (scanInterval != this.scanInterval) {
            cancelScan();
        }
        this.scanInterval = scanInterval;
        startScan();
    }

    public boolean isScanEnabled() {
        return scanEnabled;
    }

    /** {@inheritDoc} */
    public synchronized void startScanner() {
        final boolean scanEnabled = this.scanEnabled;
        if(scanEnabled) {
            return;
        }
        this.scanEnabled = true;
        startScan();
    }

    /** {@inheritDoc} */
    public synchronized void stopScanner() {
        this.scanEnabled = false;
        cancelScan();
    }


    // ---------------------------------------------------------------  protected

    void setDeploymentManager(ServerDeploymentManager deploymentManager) {
        this.deploymentManager = deploymentManager;
    }

    void setScheduledExecutor(ScheduledExecutorService scheduledExecutor) {
        this.scheduledExecutor = scheduledExecutor;
    }

    void setServerModel(ServerModel serverModel) {
        this.serverModel = serverModel;
    }

    void validateAndCreate() {

        if(scheduledExecutor == null) {
            throw new IllegalStateException("null scheduled executor");
        }
        if(deploymentManager == null) {
            throw new IllegalStateException("null deployment manager");
        }
        if(serverModel == null) {
            throw new IllegalStateException("null server model");
        }
        if(deploymentDir == null) {
            throw new IllegalStateException("null deployment dir");
        }
        if (!deploymentDir.exists()) {
            throw new IllegalArgumentException(deploymentDir.getAbsolutePath() + " does not exist");
        }
        if (!deploymentDir.isDirectory()) {
            throw new IllegalArgumentException(deploymentDir.getAbsolutePath() + " is not a directory");
        }
        if (!deploymentDir.canWrite()) {
            throw new IllegalArgumentException(deploymentDir.getAbsolutePath() + " is not writable");
        }
        // Build list of existing ".deployed" files
        establishDeployedContentList(deploymentDir);

        startScan();

        log.infof("Started %s for directory %s", getClass().getSimpleName(), deploymentDir.getAbsolutePath());
    }

    private void establishDeployedContentList(File dir) {
        ServerModel serverModel = this.serverModel;
        File[] children = dir.listFiles();
        for (File child : children) {
            String fileName = child.getName();
            if (child.isDirectory()) {
                // TODO special handling for exploded content
                establishDeployedContentList(child);
            }
            else if (fileName.endsWith(DEPLOYED)) {
                String deploymentName = fileName.substring(0, fileName.length() - DEPLOYED.length());
                if (serverModel.getDeployment(deploymentName) != null) {
                    // ServerModel knows about this deployment
                    deployed.add(deploymentName);
                }
                else {
                    // ServerModel doesn't know about this deployment, so the
                    // marker file needs to be removed.
                    if (!child.delete()) {
                        log.warnf("Cannot removed extraneous deployment marker file %s", fileName);
                    }
                }
            }
        }
    }

    private void scan() {

        try {
            scanLock.lockInterruptibly();
        }
        catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            return;
        }
        try {
            if (scanEnabled) { // confirm the scan is still wanted

                log.tracef("Scanning directory %s for deployment content changes", deploymentDir.getAbsolutePath());

                DeploymentPlanBuilder builder = this.deploymentManager.newDeploymentPlan();
                Map<String, File> foundDeployed = new HashMap<String, File>();
                Set<String> newlyAdded = new HashSet<String>();
                builder = scanDirectory(deploymentDir, builder, foundDeployed, newlyAdded);

                // Add remove actions to the plan for anything we count as
                // deployed that we didn't find on the scan
                Set<String> toRemove = new HashSet<String>(deployed);
                toRemove.removeAll(foundDeployed.keySet());
                toRemove.removeAll(newlyAdded); // in case user removed the marker and added replacement
                for (String missing : toRemove) {
                    builder = builder.undeploy(missing).andRemoveUndeployed();
                }

                // Throw away any found marker files that we didn't already know about
                Set<String> validFinds = cleanSpuriousMarkerFiles(foundDeployed);
                validFinds.addAll(newlyAdded);
                this.deployed = validFinds;

                DeploymentPlan plan = builder.build();

                if (plan.getDeploymentActions().size() > 0) {
                    if (log.isDebugEnabled()) {
                        for (DeploymentAction action : plan.getDeploymentActions()) {
                            log.debugf("Deployment plan %s includes action of type %s affecting deployment %s", plan.getId(), action.getType(), action.getDeploymentUnitUniqueName());
                        }
                    }
                    Future<ServerDeploymentPlanResult> future = deploymentManager.execute(plan);

                    try {
                        ServerDeploymentPlanResult result = future.get(60, TimeUnit.SECONDS);
                        // FIXME deal with result
                    } catch (TimeoutException e) {
                        // This could be a WARN but deployments could validly take over 60 seconds
                        log.infof("Deployment plan %s did not complete within 60 seconds. Resuming scanning for deployment changes.", plan.getId());
                    }
                }

                log.tracef("Scan complete");
            }
        } catch (InterruptedException e) {
            log.warn("Interrupted waiting on completion of deployment plan");
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            log.error("Retrieval of " + ServerDeploymentPlanResult.class.getName() + " threw an exception.", e);
            // FIXME any other handling?
        }
        finally {
            scanLock.unlock();
        }
    }

    private Set<String> cleanSpuriousMarkerFiles(Map<String, File> found) {
        Set<String> valid = new HashSet<String>();

        for (Map.Entry<String, File> entry : found.entrySet()) {
            if (deployed.contains(entry.getKey())) {
                valid.add(entry.getKey());
            }
            else {
                // Try and clean up
                entry.getValue().delete();
            }
        }
        return valid;
    }

    /**
     * Scan the given directory for content changes.
     *
     * @param directory the directory to scan
     * @param builder the builder to use to add deployment actions as new content is found
     * @param foundDeployed place to store marker files found in the directory; key is the name
     *           of the deployment, value is the marker file
     * @param newlyAdded place to store names of newly added content
     *
     * @return the builder the current builder following any changes
     */
    private DeploymentPlanBuilder scanDirectory(File directory, DeploymentPlanBuilder builder, Map<String, File> foundDeployed, Set<String> newlyAdded) {

        //TODO externalize config of filter?
        File[] children = directory.listFiles(filter);
        if (children == null) {
            return builder;
        }

        for (File child : children) {

            String fileName = child.getName();

            if (fileName.endsWith(DEPLOYED)) {
                String origName = fileName.substring(0, fileName.length() - DEPLOYED.length());
                foundDeployed.put(origName, child);
            }
            else if (child.isDirectory()) {
                int idx = fileName.lastIndexOf('.');
                if (idx > -1 && ARCHIVES.contains(fileName.substring(idx))) {
                    // FIXME handle exploded deployments
                    log.warnf("%s is an exploded deployment and exploded deployments are not currently handled by %s", child.getName(), getClass().getSimpleName());
                }
                else {
                    // It's just a dir for organizing content. Recurse
                    builder = scanDirectory(child, builder, foundDeployed, newlyAdded);
                }
            }
            else {
                // Found a single non-marker file

                DeploymentPlanBuilder currentBuilder = builder;

                boolean uploaded = false;
                boolean replace = false;
                ServerGroupDeploymentElement deployment = serverModel.getDeployment(fileName);
                if (deployment != null) {
                    if (deployment.isStart()) {
                        replace = true;
                    }
                    else {
                        // A replace(child) will not result in deploying the new
                        // content, which is not the semantic we want with
                        // filesystem hot deployment. So clean out the
                        // existing deployment from the config and do a new
                        // add+deploy below
                        builder = builder.remove(fileName);
                    }
                }

                try {
                    if (replace) {
                        builder = builder.replace(child);
                    }
                    else {
                        builder = builder.add(child).andDeploy();
                    }
                    uploaded = true;
                } catch (IOException e) {
                    log.errorf(e, "Failed adding deployment content at %s", child.getAbsolutePath());
                } catch (DuplicateDeploymentNameException e) {
                    // Content with same name must have been added via some
                    // other means. Warn and replace
                    log.warnf("Deployment content with name %s is already installed " +
                            "but was unknown to this filesystem deployment scanner. " +
                            "Replacing the existing content with new content %s.", fileName, child.getAbsolutePath());
                    try {
                        builder = builder.replace(child);
                        uploaded = true;
                    } catch (IOException e1) {
                        log.errorf(e1, "Failed replacing %s with content at %s", fileName, child.getAbsolutePath());
                    }
                }

                if (uploaded) {
                    if (replaceWithDeployedMarker(child)) {
                        newlyAdded.add(fileName);
                    }
                    else {
                        // Discard the deployment plan work done in this step
                        builder = currentBuilder;
                    }
                }
            }
        }
        return builder;
    }

    /** Adds a marker file, deletes the regular content file */
    private boolean replaceWithDeployedMarker(File child) {
        boolean ok = false;
        File marker = new File(child.getParent(), child.getName() + DEPLOYED);
        FileOutputStream fos = null;
        try {
            marker.createNewFile();
            fos = new FileOutputStream(marker);
            fos.write(child.getName().getBytes());
            ok = true;
        }
        catch (IOException io) {
            log.errorf(io, "Caught exception writing deployment marker file %s", marker.getAbsolutePath());
        }
        finally {
            if (fos != null) {
                try {
                    fos.close();
                }
                catch (IOException ignored) {
                    log.warnf("Could not close output stream for deployment marker file %s", marker.getAbsolutePath());
                }
            }
        }

        if (ok) {
            ok = child.delete();
            if (!ok) {
                log.errorf("Cannot remove deployment content file %s", child.getAbsolutePath());
                marker.delete();
            }
        }

        return ok;
    }

    private synchronized void startScan() {

        if (scanEnabled) {

            Runnable r = new Runnable() {
                public void run() {
                    try {
                        scan();
                    } catch (RuntimeException e) {
                        log.errorf(e, "Scan of %s threw RuntimeException", deploymentDir.getAbsolutePath());
                    }
                }
            };

            if (scanInterval > 0) {
                scanTask = scheduledExecutor.scheduleWithFixedDelay(r, 0, scanInterval, TimeUnit.MILLISECONDS);
            }
            else {
                scanTask = scheduledExecutor.schedule(r, scanInterval, TimeUnit.MILLISECONDS);
            }
        }
    }

    /** Invoke with the object monitor held */
    private void cancelScan() {
        if (scanTask != null) {
            scanTask.cancel(false);
            scanTask = null;
        }
    }

}
TOP

Related Classes of org.jboss.as.server.standalone.deployment.FileSystemDeploymentService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.