| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.openejb.config; |
| |
| import org.apache.openejb.assembler.classic.AppInfo; |
| import org.apache.openejb.assembler.classic.Assembler; |
| import org.apache.openejb.assembler.classic.event.ContainerSystemPostCreate; |
| import org.apache.openejb.assembler.classic.event.ContainerSystemPreDestroy; |
| import org.apache.openejb.config.sys.Deployments; |
| import org.apache.openejb.loader.FileUtils; |
| import org.apache.openejb.loader.Files; |
| import org.apache.openejb.loader.Options; |
| import org.apache.openejb.loader.SystemInstance; |
| import org.apache.openejb.observer.Observes; |
| import org.apache.openejb.util.Duration; |
| import org.apache.openejb.util.LogCategory; |
| import org.apache.openejb.util.Logger; |
| |
| import java.io.File; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class AutoDeployer { |
| |
| private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, AutoDeployer.class); |
| |
| private final ConfigurationFactory factory; |
| private final long pollIntervalMillis; |
| private final Map<String, FileInfo> files = new HashMap<String, FileInfo>(); |
| private final Timer timer; |
| private final List<Deployments> deployments = new ArrayList<Deployments>(); |
| |
| public AutoDeployer(final ConfigurationFactory factory, final List<Deployments> deployments) { |
| final Options options = SystemInstance.get().getOptions(); |
| final Duration interval = options.get("openejb.autodeploy.interval", new Duration(2, TimeUnit.SECONDS)); |
| |
| if (interval.getUnit() == null) interval.setUnit(TimeUnit.SECONDS); |
| |
| this.factory = factory; |
| this.deployments.addAll(deployments); |
| this.pollIntervalMillis = interval.getUnit().toMillis(interval.getTime()); |
| this.timer = new Timer(this.getClass().getSimpleName()); |
| } |
| |
| public synchronized boolean fileAdded(final File file) { |
| logger.info("Auto-Deploying: " + file.getAbsolutePath()); |
| try { |
| final AppInfo appInfo = factory.configureApplication(file); |
| appInfo.paths.add(file.getAbsolutePath()); |
| appInfo.paths.add(appInfo.path); |
| for (final String path : appInfo.paths) { |
| logger.info("Auto-Deploy: Path " + path); |
| } |
| getAssembler().createApplication(appInfo); |
| } catch (Exception e) { |
| logger.error("Auto-Deploy Failed: " + file.getAbsolutePath(), e); |
| } |
| return true; |
| } |
| |
| private Assembler getAssembler() { |
| return SystemInstance.get().getComponent(Assembler.class); |
| } |
| |
| public synchronized boolean fileRemoved(final File file) { |
| final String path = file.getAbsolutePath(); |
| final Collection<AppInfo> apps = getAssembler().getDeployedApplications(); |
| for (final AppInfo app : apps) { |
| if (app.paths.contains(path)) { |
| logger.info("Auto-Undeploying: " + app.appId + " - " + file.getAbsolutePath()); |
| try { |
| getAssembler().destroyApplication(app); |
| for (final String location : app.paths) { |
| final File delete = new File(location); |
| Files.remove(delete); |
| logger.info("Auto-Undeploy: Delete " + location); |
| } |
| } catch (Exception e) { |
| logger.error("Auto-Undeploy Failed: " + file.getAbsolutePath(), e); |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| public void fileUpdated(final File file) { |
| fileRemoved(file); |
| fileAdded(file); |
| } |
| |
| private Logger getLogger() { |
| return logger; |
| } |
| |
| @SuppressWarnings("UnusedParameters") |
| public void observe(@Observes final ContainerSystemPostCreate postCreate) { |
| start(); |
| } |
| |
| @SuppressWarnings("UnusedParameters") |
| public void observe(@Observes final ContainerSystemPreDestroy preDestroy) { |
| stop(); |
| } |
| |
| public synchronized void stop() { |
| timer.cancel(); |
| } |
| |
| public void start() { |
| initialize(); |
| |
| getLogger().debug("Scanner running. Polling every " + pollIntervalMillis + " milliseconds."); |
| |
| timer.scheduleAtFixedRate(new TimerTask() { |
| @Override |
| public void run() { |
| try { |
| scan(); |
| } catch (Exception e) { |
| getLogger().error("Scan failed.", e); |
| } |
| } |
| }, pollIntervalMillis, pollIntervalMillis); |
| |
| } |
| |
| private void initialize() { |
| getLogger().debug("Doing initial scan of Deployments"); |
| |
| for (final File file : list()) { |
| |
| if (!file.canRead()) { |
| continue; |
| } |
| |
| final FileInfo now = newInfo(file); |
| now.setChanging(false); |
| } |
| } |
| |
| private FileInfo newInfo(final File child) { |
| final FileInfo fileInfo = child.isDirectory() ? new DirectoryInfo(child) : new FileInfo(child); |
| files.put(fileInfo.getPath(), fileInfo); |
| return fileInfo; |
| } |
| |
| /** |
| * Looks for changes to the immediate contents of the directory we're watching. |
| */ |
| public void scan() { |
| |
| final List<File> files = list(); |
| |
| final HashSet<String> missingFilesList = new HashSet<String>(this.files.keySet()); |
| |
| for (final File file : files) { |
| |
| missingFilesList.remove(file.getAbsolutePath()); |
| |
| if (!file.canRead()) { |
| getLogger().debug("not readable " + file.getName()); |
| continue; |
| } |
| |
| final FileInfo oldStatus = oldInfo(file); |
| final FileInfo newStatus = newInfo(file); |
| |
| newStatus.diff(oldStatus); |
| |
| if (oldStatus == null) { |
| // Brand new, but assume it's changing and |
| // wait a bit to make sure it's not still changing |
| getLogger().debug("File Discovered: " + newStatus); |
| } else if (newStatus.isChanging()) { |
| // The two records are different -- record the latest as a file that's changing |
| // and later when it stops changing we'll do the add or update as appropriate. |
| getLogger().debug("File Changing: " + newStatus); |
| } else if (oldStatus.isNewFile()) { |
| // Used to be changing, now in (hopefully) its final state |
| getLogger().info("New File: " + newStatus); |
| newStatus.setNewFile(!fileAdded(file)); |
| } else if (oldStatus.isChanging()) { |
| getLogger().info("Updated File: " + newStatus); |
| fileUpdated(file); |
| |
| missingFilesList.remove(oldStatus.getPath()); |
| } |
| // else it's just totally unchanged and we ignore it this pass |
| } |
| |
| // Look for any files we used to know about but didn't find in this pass |
| for (final String path : missingFilesList) { |
| getLogger().info("File removed: " + path); |
| |
| if (fileRemoved(new File(path))) { |
| this.files.remove(path); |
| } |
| } |
| } |
| |
| private List<File> list() { |
| final List<File> files = new ArrayList<File>(); |
| |
| { // list all the files associated with hot deploy locations |
| |
| final FileUtils base = SystemInstance.get().getBase(); |
| for (final Deployments deployment : deployments) { |
| DeploymentsResolver.loadFrom(deployment, base, files); |
| } |
| } |
| return files; |
| } |
| |
| private FileInfo oldInfo(final File file) { |
| return files.get(file.getAbsolutePath()); |
| } |
| |
| /** |
| * Provides details about a directory. |
| */ |
| private static class DirectoryInfo extends FileInfo { |
| public DirectoryInfo(final File dir) { |
| // |
| // We don't pay attention to the size of the directory or files in the |
| // directory, only the highest last modified time of anything in the |
| // directory. Hopefully this is good enough. |
| // |
| super(dir.getAbsolutePath(), 0, getLastModifiedInDir(dir)); |
| } |
| |
| private static long getLastModifiedInDir(final File dir) { |
| assert dir != null; |
| |
| long value = dir.lastModified(); |
| final File[] children = dir.listFiles(); |
| long test; |
| |
| if (children != null) { |
| for (final File child : children) { |
| if (!child.canRead()) { |
| continue; |
| } |
| |
| if (child.isDirectory()) { |
| test = getLastModifiedInDir(child); |
| } else { |
| test = child.lastModified(); |
| } |
| |
| if (test > value) { |
| value = test; |
| } |
| } |
| } |
| |
| return value; |
| } |
| } |
| |
| /** |
| * Provides details about a file. |
| */ |
| private static class FileInfo implements Serializable { |
| private final String path; |
| |
| private long size; |
| |
| private long modified; |
| |
| private boolean newFile; |
| |
| private boolean changing; |
| |
| public FileInfo(final File file) { |
| this(file.getAbsolutePath(), file.length(), file.lastModified()); |
| } |
| |
| public FileInfo(final String path, final long size, final long modified) { |
| assert path != null; |
| |
| this.path = path; |
| this.size = size; |
| this.modified = modified; |
| this.newFile = true; |
| this.changing = true; |
| } |
| |
| public String getPath() { |
| return path; |
| } |
| |
| public long getSize() { |
| return size; |
| } |
| |
| public void setSize(final long size) { |
| this.size = size; |
| } |
| |
| public long getModified() { |
| return modified; |
| } |
| |
| public void setModified(final long modified) { |
| this.modified = modified; |
| } |
| |
| public boolean isNewFile() { |
| return newFile; |
| } |
| |
| public void setNewFile(final boolean newFile) { |
| this.newFile = newFile; |
| } |
| |
| public boolean isChanging() { |
| return changing; |
| } |
| |
| public void setChanging(final boolean changing) { |
| this.changing = changing; |
| } |
| |
| public boolean isSame(final FileInfo info) { |
| assert info != null; |
| |
| if (!path.equals(info.path)) { |
| throw new IllegalArgumentException("Should only be used to compare two files representing the same path!"); |
| } |
| |
| return size == info.size && modified == info.modified; |
| } |
| |
| public String toString() { |
| return path; |
| } |
| |
| public void diff(final FileInfo old) { |
| if (old != null) { |
| this.changing = !isSame(old); |
| this.newFile = old.newFile; |
| } |
| } |
| } |
| } |