Home » geronimo-2.2-source-release » org.apache.geronimo.deployment.hot » [javadoc | source]

    1   /**
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    */
   17   package org.apache.geronimo.deployment.hot;
   18   
   19   import org.slf4j.LoggerFactory;
   20   import org.slf4j.Logger;
   21   import org.apache.geronimo.deployment.cli.DeployUtils;
   22   import org.apache.geronimo.deployment.util.DeploymentUtil;
   23   import org.apache.geronimo.kernel.repository.Artifact;
   24   import org.apache.geronimo.kernel.config.IOUtil;
   25   
   26   import java.io.File;
   27   import java.io.Serializable;
   28   import java.io.IOException;
   29   import java.util.Map;
   30   import java.util.HashMap;
   31   import java.util.HashSet;
   32   import java.util.Iterator;
   33   import java.util.List;
   34   import java.util.LinkedList;
   35   import java.util.Set;
   36   
   37   /**
   38    * Meant to be run as a Thread that tracks the contents of a directory.
   39    * It sends notifications for changes to its immediate children (it
   40    * will look into subdirs for changes, but will not send notifications
   41    * for files within subdirectories).  If a file continues to change on
   42    * every pass, this will wait until it stabilizes before sending an
   43    * add or update notification (to handle slow uploads, etc.).
   44    *
   45    * @version $Rev: 673093 $ $Date: 2008-07-01 08:08:51 -0700 (Tue, 01 Jul 2008) $
   46    */
   47   public class DirectoryMonitor implements Runnable {
   48       private static final Logger log = LoggerFactory.getLogger(DirectoryMonitor.class);
   49   
   50       public static interface Listener {
   51           /**
   52            * The directory monitor doesn't take any action unless this method
   53            * returns true (to avoid deploying before the deploy GBeans are
   54            * running, etc.).
   55            */
   56           boolean isServerRunning();
   57   
   58           /**
   59            * Checks if the file with same configID is already deployed 
   60            *
   61            * @return true if the file in question is already available in the
   62            *         server, false if it should be deployed on the next pass.
   63            */
   64           boolean isFileDeployed(File file, String configId);
   65   
   66           /**
   67            * Called during initialization on previously deployed files.
   68            *
   69            * @return The time that the file was deployed.  If the current
   70            *         version in the directory is newer, the file will be
   71            *         updated on the first pass.
   72            */
   73           long getDeploymentTime(File file, String configId);
   74   
   75           /**
   76            * Called to indicate that the monitor has fully initialized
   77            * and will be doing normal deployment operations from now on.
   78            */
   79           void started();
   80   
   81           /**
   82            * Called to check whether a file passes the smell test before
   83            * attempting to deploy it.
   84            *
   85            * @return true if there's nothing obviously wrong with this file.
   86            *         false if there is (for example, it's clearly not
   87            *         deployable).
   88            */
   89           boolean validateFile(File file, String configId);
   90   
   91           /**
   92            * @return A configId for the deployment if the addition was processed
   93            *         successfully (or an empty String if the addition was OK but
   94            *         the configId could not be determined).  null if the addition
   95            *         failed, in which case the file will be added again next time
   96            *         it changes.
   97            */
   98           String fileAdded(File file);
   99   
  100           /**
  101            * @return true if the removal was processed successfully.  If not
  102            *         the file will be removed again on the next pass.
  103            */
  104           boolean fileRemoved(File file, String configId);
  105   
  106           String fileUpdated(File file, String configId);
  107   
  108           /**
  109            * This method returns the module id of an application deployed in the default group.
  110            * @return String respresenting the ModuleId if the application is already deployed
  111            */
  112           String getModuleId(String config);
  113   
  114       }
  115   
  116       private int pollIntervalMillis;
  117       private File directory;
  118       private boolean done = false;
  119       private Listener listener; // a little cheesy, but do we really need multiple listeners?
  120       private final Map files = new HashMap();
  121       private volatile String workingOnConfigId;
  122   
  123       public DirectoryMonitor(File directory, Listener listener, int pollIntervalMillis) {
  124           this.directory = directory;
  125           this.listener = listener;
  126           this.pollIntervalMillis = pollIntervalMillis;
  127       }
  128   
  129       public int getPollIntervalMillis() {
  130           return pollIntervalMillis;
  131       }
  132   
  133       public void setPollIntervalMillis(int pollIntervalMillis) {
  134           this.pollIntervalMillis = pollIntervalMillis;
  135       }
  136   
  137       public Listener getListener() {
  138           return listener;
  139       }
  140   
  141       public void setListener(Listener listener) {
  142           this.listener = listener;
  143       }
  144   
  145       public File getDirectory() {
  146           return directory;
  147       }
  148   
  149       /**
  150        * Warning: changing the directory at runtime will cause all files in the
  151        * old directory to be removed and all files in the new directory to be
  152        * added, next time the thread awakens.
  153        */
  154       public void setDirectory(File directory) {
  155           if (!directory.isDirectory() || !directory.canRead()) {
  156               throw new IllegalArgumentException("Cannot monitor directory " + directory.getAbsolutePath());
  157           }
  158           this.directory = directory;
  159       }
  160   
  161       public synchronized boolean isDone() {
  162           return done;
  163       }
  164   
  165       public synchronized void close() {
  166           this.done = true;
  167       }
  168   
  169       public void removeModuleId(Artifact id) {
  170           log.info("Hot deployer notified that an artifact was removed: "+id);
  171           if(id.toString().equals(workingOnConfigId)) {
  172               // since the redeploy process inserts a new thread to handle progress,
  173               // this is called by a different thread than the hot deploy thread during
  174               // a redeploy, and this check must be executed outside the synchronized
  175               // block or else it will cause a deadlock!
  176               return; // don't react to events we generated ourselves
  177           }
  178           synchronized(files) {
  179               for (Iterator it = files.keySet().iterator(); it.hasNext();) {
  180                   String path = (String) it.next();
  181                   FileInfo info = (FileInfo) files.get(path);
  182                   Artifact target = Artifact.create(info.getConfigId());
  183                   if(id.matches(target)) { // need to remove record & delete file
  184                       File file = new File(path);
  185                       if(file.exists()) { // if not, probably it's deletion kicked off this whole process
  186                           log.info("Hot deployer deleting "+id);
  187                           if(!IOUtil.recursiveDelete(file)) {
  188                               log.error("Hot deployer unable to delete "+path);
  189                           }
  190                           it.remove();
  191                       }
  192                   }
  193               }
  194           }
  195       }
  196   
  197       public void run() {
  198           boolean serverStarted = false, initialized = false;
  199           while (!done) {
  200               try {
  201                   Thread.sleep(pollIntervalMillis);
  202               } catch (InterruptedException e) {
  203                   continue;
  204               }
  205               try {
  206                   if (listener != null) {
  207                       if (!serverStarted && listener.isServerRunning()) {
  208                           serverStarted = true;
  209                       }
  210                       if (serverStarted) {
  211                           if (!initialized) {
  212                               initialized = true;
  213                               initialize();
  214                               listener.started();
  215                           } else {
  216                               scanDirectory();
  217                           }
  218                       }
  219                   }
  220               } catch (Exception e) {
  221                   log.error("Error during hot deployment", e);
  222               }
  223           }
  224       }
  225   
  226       public void initialize() {
  227           File parent = directory;
  228           File[] children = parent.listFiles();
  229           for (int i = 0; i < children.length; i++) {
  230               File child = children[i];
  231               if (!child.canRead()) {
  232                   continue;
  233               }
  234               FileInfo now = child.isDirectory() ? getDirectoryInfo(child) : getFileInfo(child);
  235               now.setChanging(false);
  236               try {
  237                   now.setConfigId(calculateModuleId(child));
  238                   if (listener == null || listener.isFileDeployed(child, now.getConfigId())) {
  239                       if (listener != null) {
  240                           now.setModified(listener.getDeploymentTime(child, now.getConfigId()));
  241                       }
  242   log.info("At startup, found "+now.getPath()+" with deploy time "+now.getModified()+" and file time "+new File(now.getPath()).lastModified());
  243                       files.put(now.getPath(), now);
  244                   }
  245               } catch (Exception e) {
  246                   log.error("Unable to scan file " + child.getAbsolutePath() + " during initialization", e);
  247               }
  248           }
  249       }
  250   
  251       /**
  252        * Looks for changes to the immediate contents of the directory we're watching.
  253        */
  254       private void scanDirectory() {
  255           File parent = directory;
  256           File[] children = parent.listFiles();
  257           if (!directory.exists() || children == null) {
  258               log.error("Hot deploy directory has disappeared!  Shutting down directory monitor.");
  259               done = true;
  260               return;
  261           }
  262           synchronized (files) {
  263               Set oldList = new HashSet(files.keySet());
  264               List actions = new LinkedList();
  265               for (int i = 0; i < children.length; i++) {
  266                   File child = children[i];
  267                   if (!child.canRead()) {
  268                       continue;
  269                   }
  270                   FileInfo now = child.isDirectory() ? getDirectoryInfo(child) : getFileInfo(child);
  271                   FileInfo then = (FileInfo) files.get(now.getPath());
  272                   if (then == null) { // Brand new, wait a bit to make sure it's not still changing
  273                       now.setNewFile(true);
  274                       files.put(now.getPath(), now);
  275                       log.debug("New File: " + now.getPath());
  276                   } else {
  277                       oldList.remove(then.getPath());
  278                       if (now.isSame(then)) { // File is the same as the last time we scanned it
  279                           if (then.isChanging()) {
  280                               log.debug("File finished changing: " + now.getPath());
  281                               // Used to be changing, now in (hopefully) its final state
  282                               if (then.isNewFile()) {
  283                                   actions.add(new FileAction(FileAction.NEW_FILE, child, then));
  284                               } else {
  285                                   actions.add(new FileAction(FileAction.UPDATED_FILE, child, then));
  286                               }
  287                               then.setChanging(false);
  288                           } // else it's just totally unchanged and we ignore it this pass
  289                       } else if(then.isNewFile() || now.getModified() > then.getModified()) {
  290                           // The two records are different -- record the latest as a file that's changing
  291                           // and later when it stops changing we'll do the add or update as appropriate.
  292                           now.setConfigId(then.getConfigId());
  293                           now.setNewFile(then.isNewFile());
  294                           files.put(now.getPath(), now);
  295                           log.debug("File Changed: " + now.getPath());
  296                       }
  297                   }
  298               }
  299               // Look for any files we used to know about but didn't find in this pass
  300               for (Iterator it = oldList.iterator(); it.hasNext();) {
  301                   String name = (String) it.next();
  302                   FileInfo info = (FileInfo) files.get(name);
  303                   log.debug("File removed: " + name);
  304                   if (info.isNewFile()) { // Was never added, just whack it
  305                       files.remove(name);
  306                   } else {
  307                       actions.add(new FileAction(FileAction.REMOVED_FILE, new File(name), info));
  308                   }
  309               }
  310               if (listener != null) {
  311                   // First pass: validate all changed files, so any obvious errors come out first
  312                   for (Iterator it = actions.iterator(); it.hasNext();) {
  313                       FileAction action = (FileAction) it.next();
  314                       if (!listener.validateFile(action.child, action.info.getConfigId())) {
  315                           resolveFile(action);
  316                           it.remove();
  317                       }
  318                   }
  319                   // Second pass: do what we're meant to do
  320                   for (Iterator it = actions.iterator(); it.hasNext();) {
  321                       FileAction action = (FileAction) it.next();
  322                       try {
  323                           if (action.action == FileAction.REMOVED_FILE) {
  324                               workingOnConfigId = action.info.getConfigId();
  325                               if (action.info.getConfigId() == null || listener.fileRemoved(action.child, action.info.getConfigId())) {
  326                                   files.remove(action.child.getPath());
  327                               }
  328                               workingOnConfigId = null;
  329                           } else if (action.action == FileAction.NEW_FILE) {
  330                               if (listener.isFileDeployed(action.child, calculateModuleId(action.child))) {
  331                                   workingOnConfigId = calculateModuleId(action.child);
  332                                   String result = listener.fileUpdated(action.child, workingOnConfigId);
  333                                   if (result != null) {
  334                                       if (!result.equals("")) {
  335                                           action.info.setConfigId(result);
  336                                       }
  337                                       else {
  338                                           action.info.setConfigId(calculateModuleId(action.child));
  339                                       }
  340                                   }
  341                                   // remove the previous jar or directory if duplicate
  342                                   File[] childs = directory.listFiles();
  343                                   for (int i = 0; i < childs.length; i++) {
  344                                       String path = childs[i].getAbsolutePath();
  345                                       String configId = ((FileInfo)files.get(path)).configId;
  346                                       if (configId != null && configId.equals(workingOnConfigId) && !action.child.getAbsolutePath().equals(path)) {
  347                                           File fd = new File(path);
  348                                           if (fd.isDirectory()) {
  349                                               log.info("Deleting the Directory: "+path);
  350                                               if (DeploymentUtil.recursiveDelete(fd))
  351                                                   log.debug("Successfully deleted the Directory: "+path);
  352                                               else
  353                                                   log.error("Couldn't delete the hot deployed directory="+path);
  354                                           }
  355                                           else if (fd.isFile()) {
  356                                               log.info("Deleting the File: "+path);
  357                                               if (fd.delete()) {
  358                                                   log.debug("Successfully deleted the File: "+path); 
  359                                               }
  360                                               else
  361                                                   log.error("Couldn't delete the hot deployed file="+path); 
  362                                           }
  363                                           files.remove(path);
  364                                       }
  365                                   }
  366                                   workingOnConfigId = null;
  367                               }
  368                               else {
  369                                   String result = listener.fileAdded(action.child);
  370                                   if (result != null) {
  371                                       if (!result.equals("")) {
  372                                           action.info.setConfigId(result);
  373                                       }
  374                                       else {
  375                                           action.info.setConfigId(calculateModuleId(action.child));
  376                                       }
  377                                   }
  378                               }
  379                               action.info.setNewFile(false);
  380                           } else if (action.action == FileAction.UPDATED_FILE) {
  381                               workingOnConfigId = action.info.getConfigId();
  382                               String result = listener.fileUpdated(action.child, action.info.getConfigId());
  383                               FileInfo update = action.info;
  384                               if (result != null) {
  385                                   if (!result.equals("")) {
  386                                       update.setConfigId(result);
  387                                   } else {
  388                                       update.setConfigId(calculateModuleId(action.child));
  389                                   }
  390                               }
  391                               workingOnConfigId = null;
  392                           }
  393                       } catch (Exception e) {
  394                           log.error("Unable to " + action.getActionName() + " file " + action.child.getAbsolutePath(), e);
  395                       } finally {
  396                           resolveFile(action);
  397                       }
  398                   }
  399               }
  400           }
  401       }
  402   
  403       private void resolveFile(FileAction action) {
  404           if (action.action == FileAction.REMOVED_FILE) {
  405               files.remove(action.child.getPath());
  406           } else {
  407               action.info.setChanging(false);
  408           }
  409       }
  410   
  411       private String calculateModuleId(File module) {
  412           String moduleId = null;
  413           try {
  414               moduleId = DeployUtils.extractModuleIdFromArchive(module);
  415           } catch (Exception e) {
  416               try {
  417                   moduleId = DeployUtils.extractModuleIdFromPlan(module);
  418               } catch (IOException e2) {
  419                   log.warn("Unable to calculate module ID for file " + module.getAbsolutePath() + " [" + e2.getMessage() + "]");
  420               }
  421           }
  422           if (moduleId == null) {
  423               int pos = module.getName().lastIndexOf('.');
  424               moduleId = pos > -1 ? module.getName().substring(0, pos) : module.getName();
  425               moduleId = listener.getModuleId(moduleId);
  426           }
  427           return moduleId;
  428       }
  429   
  430       /**
  431        * We don't pay attention to the size of the directory or files in the
  432        * directory, only the highest last modified time of anything in the
  433        * directory.  Hopefully this is good enough.
  434        */
  435       private FileInfo getDirectoryInfo(File dir) {
  436           FileInfo info = new FileInfo(dir.getAbsolutePath());
  437           info.setSize(0);
  438           info.setModified(getLastModifiedInDir(dir));
  439           return info;
  440       }
  441   
  442       private long getLastModifiedInDir(File dir) {
  443           long value = dir.lastModified();
  444           File[] children = dir.listFiles();
  445           long test;
  446           for (int i = 0; i < children.length; i++) {
  447               File child = children[i];
  448               if (!child.canRead()) {
  449                   continue;
  450               }
  451               if (child.isDirectory()) {
  452                   test = getLastModifiedInDir(child);
  453               } else {
  454                   test = child.lastModified();
  455               }
  456               if (test > value) {
  457                   value = test;
  458               }
  459           }
  460           return value;
  461       }
  462   
  463       private FileInfo getFileInfo(File child) {
  464           FileInfo info = new FileInfo(child.getAbsolutePath());
  465           info.setSize(child.length());
  466           info.setModified(child.lastModified());
  467           return info;
  468       }
  469   
  470       private static class FileAction {
  471           private static int NEW_FILE = 1;
  472           private static int UPDATED_FILE = 2;
  473           private static int REMOVED_FILE = 3;
  474           private int action;
  475           private File child;
  476           private FileInfo info;
  477   
  478           public FileAction(int action, File child, FileInfo info) {
  479               this.action = action;
  480               this.child = child;
  481               this.info = info;
  482           }
  483   
  484           public String getActionName() {
  485               return action == NEW_FILE ? "deploy" : action == UPDATED_FILE ? "redeploy" : "undeploy";
  486           }
  487       }
  488   
  489       private static class FileInfo implements Serializable {
  490           private String path;
  491           private long size;
  492           private long modified;
  493           private boolean newFile;
  494           private boolean changing;
  495           private String configId;
  496   
  497           public FileInfo(String path) {
  498               this.path = path;
  499               newFile = false;
  500               changing = true;
  501           }
  502   
  503           public String getPath() {
  504               return path;
  505           }
  506   
  507           public long getSize() {
  508               return size;
  509           }
  510   
  511           public void setSize(long size) {
  512               this.size = size;
  513           }
  514   
  515           public long getModified() {
  516               return modified;
  517           }
  518   
  519           public void setModified(long modified) {
  520               this.modified = modified;
  521           }
  522   
  523           public boolean isNewFile() {
  524               return newFile;
  525           }
  526   
  527           public void setNewFile(boolean newFile) {
  528               this.newFile = newFile;
  529           }
  530   
  531           public boolean isChanging() {
  532               return changing;
  533           }
  534   
  535           public void setChanging(boolean changing) {
  536               this.changing = changing;
  537           }
  538   
  539           public String getConfigId() {
  540               return configId;
  541           }
  542   
  543           public void setConfigId(String configId) {
  544               this.configId = configId;
  545           }
  546   
  547           public boolean isSame(FileInfo info) {
  548               if (!path.equals(info.path)) {
  549                   throw new IllegalArgumentException("Should only be used to compare two files representing the same path!");
  550               }
  551               return size == info.size && modified == info.modified;
  552           }
  553       }
  554   }

Home » geronimo-2.2-source-release » org.apache.geronimo.deployment.hot » [javadoc | source]