/** * EasyBeans * Copyright (C) 2008 Bull S.A.S. * Contact: easybeans@ow2.org * * This library 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 any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * -------------------------------------------------------------------------- * $Id: AbsMonitor.java 6088 2012-01-16 14:01:51Z benoitf $ * -------------------------------------------------------------------------- */ package org.ow2.easybeans.component.depmonitor; import static org.ow2.util.url.URLUtils.urlToFile; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.ow2.easybeans.api.EZBContainer; import org.ow2.easybeans.api.EZBContainerException; import org.ow2.easybeans.api.EZBServer; import org.ow2.easybeans.api.EmbeddedManager; import org.ow2.easybeans.server.EmbeddedException; import org.ow2.util.archive.api.ArchiveException; import org.ow2.util.archive.api.IArchive; import org.ow2.util.archive.impl.ArchiveManager; import org.ow2.util.ee.deploy.api.deployable.EJB3Deployable; import org.ow2.util.ee.deploy.api.deployable.IDeployable; import org.ow2.util.ee.deploy.api.deployer.IDeployerManager; import org.ow2.util.ee.deploy.api.helper.DeployableHelperException; import org.ow2.util.ee.deploy.impl.helper.DeployableHelper; import org.ow2.util.log.Log; import org.ow2.util.log.LogFactory; /** * Abstract class that is providing common stuff for monitors. * Implementation of monitor may extend this class. * @author Florent Benoit */ public abstract class AbsMonitor implements EZBMonitor, Runnable { /** * Sleep time for the thread of the cleaner (5s). */ private static final long SLEEP_TIME = 5000L; /** * Logger. */ private Log logger = LogFactory.getLog(AbsMonitor.class); /** * Waiting time between each scan. */ private long waitTime = SLEEP_TIME; /** * Map between a File (monitored) and the last updated file. */ private Map<File, Long> modifiedFiles = null; /** * List of deployed files (by this monitor). */ private Map<File, IDeployable<?>> deployed = null; /** * List of File that have a failed deployment (by this monitor). */ private List<File> failed = null; /** * Initializing period ?. */ private boolean bootInProgress = false; /** * Stop order received ? */ private boolean stopped = false; /** * Deployer Manager. */ private IDeployerManager deployerManager; /** * Directories to scan. */ private List<String> scanDirectories = null; /** * Directory to scan. */ private String directory = null; /** * Embedded server which is monitored. */ private EZBServer embedded = null; /** * Builds a new monitor by initializing lists. * @throws EZBMonitorException if there is an exception for this monitor. */ public AbsMonitor() throws EZBMonitorException { this.modifiedFiles = new HashMap<File, Long>(); this.deployed = new ConcurrentHashMap<File, IDeployable<?>>(); this.failed = new ArrayList<File>(); //TODO: Change these steps when EasyBeans will use all the deployer stuff // Get the embedded this.embedded = EmbeddedManager.getEmbedded(Integer.valueOf(0)); this.deployerManager = this.embedded.getDeployerManager(); } /** * Start this monitor. * @throws EZBMonitorException if it can't be started */ public void start() throws EZBMonitorException { // Start the boot process. this.bootInProgress = true; // Initialize containers try { detectNewArchives(); } catch (EZBMonitorException e) { this.logger.error("Cannot scan for new archives", e); } // No more in the boot process. this.bootInProgress = false; // Period is defined ? launch a thread if (this.waitTime > 0) { Thread thread = new Thread(this); thread.setDaemon(true); thread.setName(this.getClass().getName()); thread.start(); } } /** * Start the thread of this class It will clean all the work entries. */ public void run() { for (;;) { // Stop the thread if (this.stopped) { return; } // Check if existing EJB3 container have not been modified ? for (EZBContainer container : getEmbedded().getContainers().values()) { // Look only single EJB-JAR if (container.isAvailable() && container.getConfiguration().getApplicationName() == null) { checkContainer(container); } } // Check new archives/containers to start try { detectNewArchives(); } catch (Exception e) { // Catch all exception (including runtime) this.logger.error("Problem when trying to find and deploy new archives", e); } // Undeploy/ReDeploy archives for deployed modules try { checkModifiedDeployables(); } catch (Exception e) { // Catch all exception (including runtime) this.logger.error("Problem when checking current deployables", e); } // Thread wait try { Thread.sleep(this.waitTime); } catch (InterruptedException e) { throw new RuntimeException("Thread fail to sleep"); } } } /** * Filter to skip system file. */ private static final FilenameFilter ARCHIVE_NAME_FILTER = new FilenameFilter() { public boolean accept(final File dir, final String name) { if (name.startsWith(".")) { return false; } return true; } }; /** * Scan all files present in the deploy directory and deploy them. (if not * deployed). * @throws EZBMonitorException if there is a problem during the scan */ private void detectNewArchives() throws EZBMonitorException { // Get the list of deploy directories this.scanDirectories = Arrays.asList(this.directory); for (String deployDirectoryString : this.scanDirectories) { File deployDirectory = new File(deployDirectoryString); try { deployDirectory = deployDirectory.getCanonicalFile(); } catch (IOException e) { throw new EZBMonitorException("Cannot get file on '" + deployDirectory + "'", e); } // get files File[] files = deployDirectory.listFiles(ARCHIVE_NAME_FILTER); // next directory if there are no files to scan. if (files == null) { continue; } // analyze each file to detect new modules that are not yet // deployed. for (File f : files) { // already deployed ? if (this.deployed.containsKey(f)) { // yes, then check other files continue; } // This module has failed previously ? if (this.failed.contains(f)) { // If the module hasn't been updated, no need to deploy it // again as it will fails again if (!hasBeenUpdated(f)) { continue; } // Cleanup the previous failure and try again the deployment this.failed.remove(f); } // Else, get the deployable IArchive archive = ArchiveManager.getInstance().getArchive(f); if (archive == null) { this.logger.warn("Ignoring invalid file ''{0}''", f); continue; } IDeployable<?> deployable; try { deployable = DeployableHelper.getDeployable(archive); } catch (DeployableHelperException e) { throw new EZBMonitorException("Cannot get a deployable for the archive '" + archive + "'", e); } if (!this.bootInProgress) { // wait that files are fully copied before deploying the // files try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { throw new RuntimeException("Thread fail to sleep"); } } this.logger.debug("Detect a new Deployable ''{0}'' and deploying it.", deployable); // Now, deploy the file try { this.deployerManager.deploy(deployable); } catch (Exception e) { // Deployment of this deployable has failed this.failed.add(f); throw new EZBMonitorException("Cannot deploy the deployable '" + deployable + "'", e); } // deployed is ok this.deployed.put(f, deployable); } } } /** * Check if the given file has been updated since the last check. * @param file the file to test * @return true if the archive has been updated */ protected boolean hasBeenUpdated(final File file) { // get lastmodified for this URL long previousLastModified = 0; Long l = this.modifiedFiles.get(file); if (l != null) { previousLastModified = l.longValue(); } long updatedModified = getLastModified(file); // first check. nothing to do if (previousLastModified == 0) { // Store initial time this.modifiedFiles.put(file, Long.valueOf(updatedModified)); return false; } // URL has been updated since the last time if (updatedModified > previousLastModified) { this.modifiedFiles.put(file, Long.valueOf(updatedModified)); return true; } return false; } /** * Check if the current deployables that are deployed have been updated. If * it is the case, undeploy them and then deploy it again (except for EJB3 * Deployable where there is a stop/start). * @throws EmbeddedException if the redeployment fails */ protected void checkModifiedDeployables() throws EmbeddedException { // Get list of files that are deployed Set<File> files = this.deployed.keySet(); // Nothing to do if no modules are deployed. if (files == null) { return; } // For each deployed module that is not an EJB3, check if the module has // been updated for (File f : files) { IDeployable<?> deployable = this.deployed.get(f); // Not yet deployed ? if (deployable == null) { continue; } // EJB3 are managed in a different way if (EJB3Deployable.class.isAssignableFrom(deployable.getClass())) { continue; } // File has been removed if (!f.exists()) { // undeploy this.logger.info("Deployable ''{0}'' has been removed on the filesystem, undeploy it", deployable); try { this.deployerManager.undeploy(deployable); // Perform a garbage collector to avoid file lock during redeployment System.gc(); } catch (Exception e) { this.logger.error("Undeploy of the deployable '" + deployable + "' has failed", e); this.failed.add(f); } finally { // even in error case, the file should have been removed this.deployed.remove(f); } continue; } // Update has been detected, need to undeploy and then to deploy // again if (hasBeenUpdated(f)) { this.logger.info("Deployable ''{0}'' has been updated, reloading it", deployable); try { this.deployerManager.undeploy(deployable); this.deployed.remove(f); // Perform a garbage collector to avoid file lock during redeployment System.gc(); } catch (Exception e) { this.logger.error("Undeploy of the deployable '" + deployable + "' has failed", e); // Deployment has failed, it is now undeployed this.deployed.remove(f); this.failed.add(f); } // Get a new deployable IArchive archive = ArchiveManager.getInstance().getArchive(f); if (archive == null) { this.logger.warn("Ignoring invalid file ''{0}''", f); continue; } IDeployable<?> newDeployable; try { newDeployable = DeployableHelper.getDeployable(archive); } catch (DeployableHelperException e) { this.logger.error("Cannot get a deployable for the archive '" + archive + "'", e); continue; } try { this.deployerManager.deploy(newDeployable); // Store the new deployable this.deployed.put(f, newDeployable); // Perform a garbage collector to avoid file lock System.gc(); } catch (Exception e) { // Deployment of this deployable has failed this.failed.add(f); throw new EmbeddedException("Cannot redeploy the deployable '" + deployable + "'.", e); } } } } /** * Check a container (and its archive) and see if there is a need to reload * the container. * @param container the container to monitor. */ protected void checkContainer(final EZBContainer container) { // get archive IArchive archive = container.getArchive(); // Get URL URL url = null; try { url = archive.getURL(); } catch (ArchiveException e1) { this.logger.warn("Cannot get URL on the container {0}", archive.getName()); return; } File file = urlToFile(url); // No file archive, means that it has been removed if (!file.exists()) { this.logger.info("Archive ''{0}'' has been removed, then the associated EJB3 container is stopping", archive .getName()); try { container.stop(); getEmbedded().removeContainer(container); } finally { this.deployed.remove(file); } return; } // container was modified, need to relaunch it if (hasBeenUpdated(file)) { this.logger.info("Container with archive {0} was modified. Reloading...", archive.getName()); try { container.stop(); getEmbedded().removeContainer(container); } finally { this.deployed.remove(file); } try { container.start(); getEmbedded().addContainer(container); } catch (EZBContainerException e) { this.deployed.remove(file); this.logger.error("Error while restarting archive {0}.", archive.getName(), e); } } } /** * Gets the last modified attribute of a given archive.<br> If it is a * directory, returns the last modified file of the archive. * @param archive the archive to monitor. * @return the last modified version of the given archive. */ protected long getLastModified(final File archive) { if (archive.isFile()) { return archive.lastModified(); } // else File[] files = archive.listFiles(); long last = 0; if (files != null) { for (File f : files) { last = Math.max(last, getLastModified(f)); } } return last; } /** * Stop this monitor. * @throws EZBMonitorException if it can't be stopped */ public void stop() throws EZBMonitorException { this.stopped = true; } /** * Gets the embedded object. * @return embedded object */ public EZBServer getEmbedded() { return this.embedded; } /** * @return the waiting time. */ public long getWaitTime() { return this.waitTime; } /** * Sets the waiting time between each period. * @param waitTime the time to wait */ public void setWaitTime(final long waitTime) { this.waitTime = waitTime; } /** * @return the directory to monitor */ public String getDirectory() { return this.directory; } /** * Sets the directory to monitor. * @param directory the given directory */ public void setDirectory(final String directory) { this.directory = directory; } /** * @return the logger used by this monitor. */ public Log getLogger() { return this.logger; } }