Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » cluster » deploy » [javadoc | source]
    1   /*
    2    * Copyright 1999,2004-2005 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.apache.catalina.cluster.deploy;
   18   
   19   import java.io.File;
   20   import java.io.IOException;
   21   import java.net.URL;
   22   import java.util.HashMap;
   23   
   24   import javax.management.MBeanServer;
   25   import javax.management.ObjectName;
   26   
   27   import org.apache.catalina.Context;
   28   import org.apache.catalina.Engine;
   29   import org.apache.catalina.Host;
   30   import org.apache.catalina.Lifecycle;
   31   import org.apache.catalina.LifecycleException;
   32   import org.apache.catalina.cluster.CatalinaCluster;
   33   import org.apache.catalina.cluster.ClusterDeployer;
   34   import org.apache.catalina.cluster.ClusterMessage;
   35   import org.apache.catalina.cluster.Member;
   36   import org.apache.commons.modeler.Registry;
   37   
   38   /**
   39    * <p>
   40    * A farm war deployer is a class that is able to deploy/undeploy web
   41    * applications in WAR form within the cluster.
   42    * </p>
   43    * Any host can act as the admin, and will have three directories
   44    * <ul>
   45    * <li>deployDir - the directory where we watch for changes</li>
   46    * <li>applicationDir - the directory where we install applications</li>
   47    * <li>tempDir - a temporaryDirectory to store binary data when downloading a
   48    * war from the cluster</li>
   49    * </ul>
   50    * Currently we only support deployment of WAR files since they are easier to
   51    * send across the wire.
   52    * 
   53    * @author Filip Hanik
   54    * @author Peter Rossbach
   55    * @version 1.1
   56    */
   57   public class FarmWarDeployer implements ClusterDeployer, FileChangeListener {
   58       /*--Static Variables----------------------------------------*/
   59       public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
   60               .getLog(FarmWarDeployer.class);
   61   
   62       /*--Instance Variables--------------------------------------*/
   63       protected CatalinaCluster cluster = null;
   64   
   65       protected boolean started = false; //default 5 seconds
   66   
   67       protected HashMap fileFactories = new HashMap();
   68   
   69       protected String deployDir;
   70   
   71       protected String tempDir;
   72   
   73       protected String watchDir;
   74   
   75       protected boolean watchEnabled = false;
   76   
   77       protected WarWatcher watcher = null;
   78   
   79       /**
   80        * Iteration count for background processing.
   81        */
   82       private int count = 0;
   83   
   84       /**
   85        * Frequency of the Farm watchDir check. Cluster wide deployment will be
   86        * done once for the specified amount of backgrondProcess calls (ie, the
   87        * lower the amount, the most often the checks will occur).
   88        */
   89       protected int processDeployFrequency = 2;
   90   
   91       /**
   92        * Path where context descriptors should be deployed.
   93        */
   94       protected File configBase = null;
   95   
   96       /**
   97        * The associated host.
   98        */
   99       protected Host host = null;
  100   
  101       /**
  102        * The host appBase.
  103        */
  104       protected File appBase = null;
  105   
  106       /**
  107        * MBean server.
  108        */
  109       protected MBeanServer mBeanServer = null;
  110   
  111       /**
  112        * The associated deployer ObjectName.
  113        */
  114       protected ObjectName oname = null;
  115   
  116       /*--Constructor---------------------------------------------*/
  117       public FarmWarDeployer() {
  118       }
  119   
  120       /*--Logic---------------------------------------------------*/
  121       public void start() throws Exception {
  122           if (started)
  123               return;
  124           getCluster().addClusterListener(this);
  125           if (watchEnabled) {
  126               watcher = new WarWatcher(this, new File(getWatchDir()));
  127               if (log.isInfoEnabled())
  128                   log.info("Cluster deployment is watching " + getWatchDir()
  129                           + " for changes.");
  130           } //end if
  131   
  132           // Check to correct engine and host setup
  133           host = (Host) getCluster().getContainer();
  134           Engine engine = (Engine) host.getParent();
  135           try {
  136               oname = new ObjectName(engine.getName() + ":type=Deployer,host="
  137                       + host.getName());
  138           } catch (Exception e) {
  139               log.error("Can't construct MBean object name" + e);
  140           }
  141           configBase = new File(System.getProperty("catalina.base"), "conf");
  142           if (engine != null) {
  143               configBase = new File(configBase, engine.getName());
  144           }
  145           if (host != null) {
  146               configBase = new File(configBase, host.getName());
  147           }
  148   
  149           // Retrieve the MBean server
  150           mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
  151   
  152           started = true;
  153           count = 0;
  154           if (log.isInfoEnabled())
  155               log.info("Cluster FarmWarDeployer started.");
  156       }
  157   
  158       /*
  159        * stop cluster wide deployments
  160        * 
  161        * @see org.apache.catalina.cluster.ClusterDeployer#stop()
  162        */
  163       public void stop() throws LifecycleException {
  164           started = false;
  165           getCluster().removeClusterListener(this);
  166           count = 0;
  167           if (watcher != null) {
  168               watcher.clear();
  169               watcher = null;
  170   
  171           }
  172           if (log.isInfoEnabled())
  173               log.info("Cluster FarmWarDeployer stopped.");
  174       }
  175   
  176       public void cleanDeployDir() {
  177           throw new java.lang.UnsupportedOperationException(
  178                   "Method cleanDeployDir() not yet implemented.");
  179       }
  180   
  181       /**
  182        * Callback from the cluster, when a message is received, The cluster will
  183        * broadcast it invoking the messageReceived on the receiver.
  184        * 
  185        * @param msg
  186        *            ClusterMessage - the message received from the cluster
  187        */
  188       public void messageReceived(ClusterMessage msg) {
  189           try {
  190               if (msg instanceof FileMessage && msg != null) {
  191                   FileMessage fmsg = (FileMessage) msg;
  192                   if (log.isDebugEnabled())
  193                       log.debug("receive cluster deployment [ path: "
  194                               + fmsg.getContextPath() + " war:  "
  195                               + fmsg.getFileName() + " ]");
  196                   FileMessageFactory factory = getFactory(fmsg);
  197                   // TODO correct second try after app is in service!
  198                   if (factory.writeMessage(fmsg)) {
  199                       //last message received war file is completed
  200                       String name = factory.getFile().getName();
  201                       if (!name.endsWith(".war"))
  202                           name = name + ".war";
  203                       File deployable = new File(getDeployDir(), name);
  204                       try {
  205                           String path = fmsg.getContextPath();
  206                           if (!isServiced(path)) {
  207                               addServiced(path);
  208                               try {
  209                                   remove(path);
  210                                   factory.getFile().renameTo(deployable);
  211                                   check(path);
  212                               } finally {
  213                                   removeServiced(path);
  214                               }
  215                               if (log.isDebugEnabled())
  216                                   log.debug("deployment from " + path
  217                                           + " finished.");
  218                           } else
  219                               log.error("Application " + path
  220                                       + " in used. touch war file " + name
  221                                       + " again!");
  222                       } catch (Exception ex) {
  223                           log.error(ex);
  224                       } finally {
  225                           removeFactory(fmsg);
  226                       }
  227                   } //end if
  228               } else if (msg instanceof UndeployMessage && msg != null) {
  229                   try {
  230                       UndeployMessage umsg = (UndeployMessage) msg;
  231                       String path = umsg.getContextPath();
  232                       if (log.isDebugEnabled())
  233                           log.debug("receive cluster undeployment from " + path);
  234                       if (!isServiced(path)) {
  235                           addServiced(path);
  236                           try {
  237                               remove(path);
  238                           } finally {
  239                               removeServiced(path);
  240                           }
  241                           if (log.isDebugEnabled())
  242                               log.debug("undeployment from " + path
  243                                       + " finished.");
  244                       } else
  245                           log
  246                                   .error("Application "
  247                                           + path
  248                                           + " in used. Sorry not remove from backup cluster nodes!");
  249                   } catch (Exception ex) {
  250                       log.error(ex);
  251                   }
  252               } //end if
  253           } catch (java.io.IOException x) {
  254               log.error("Unable to read farm deploy file message.", x);
  255           }
  256       }
  257   
  258       /**
  259        * create factory for all transported war files
  260        * 
  261        * @param msg
  262        * @return Factory for all app message (war files)
  263        * @throws java.io.FileNotFoundException
  264        * @throws java.io.IOException
  265        */
  266       public synchronized FileMessageFactory getFactory(FileMessage msg)
  267               throws java.io.FileNotFoundException, java.io.IOException {
  268           File tmpFile = new File(msg.getFileName());
  269           File writeToFile = new File(getTempDir(), tmpFile.getName());
  270           FileMessageFactory factory = (FileMessageFactory) fileFactories.get(msg
  271                   .getFileName());
  272           if (factory == null) {
  273               factory = FileMessageFactory.getInstance(writeToFile, true);
  274               fileFactories.put(msg.getFileName(), factory);
  275           }
  276           return factory;
  277       }
  278   
  279       /**
  280        * Remove file (war) from messages)
  281        * 
  282        * @param msg
  283        */
  284       public void removeFactory(FileMessage msg) {
  285           fileFactories.remove(msg.getFileName());
  286       }
  287   
  288       /**
  289        * Before the cluster invokes messageReceived the cluster will ask the
  290        * receiver to accept or decline the message, In the future, when messages
  291        * get big, the accept method will only take a message header
  292        * 
  293        * @param msg
  294        *            ClusterMessage
  295        * @return boolean - returns true to indicate that messageReceived should be
  296        *         invoked. If false is returned, the messageReceived method will
  297        *         not be invoked.
  298        */
  299       public boolean accept(ClusterMessage msg) {
  300           return (msg instanceof FileMessage) || (msg instanceof UndeployMessage);
  301       }
  302   
  303       /**
  304        * Install a new web application, whose web application archive is at the
  305        * specified URL, into this container and all the other members of the
  306        * cluster with the specified context path. A context path of "" (the empty
  307        * string) should be used for the root application for this container.
  308        * Otherwise, the context path must start with a slash.
  309        * <p>
  310        * If this application is successfully installed locally, a ContainerEvent
  311        * of type <code>INSTALL_EVENT</code> will be sent to all registered
  312        * listeners, with the newly created <code>Context</code> as an argument.
  313        * 
  314        * @param contextPath
  315        *            The context path to which this application should be installed
  316        *            (must be unique)
  317        * @param war
  318        *            A URL of type "jar:" that points to a WAR file, or type
  319        *            "file:" that points to an unpacked directory structure
  320        *            containing the web application to be installed
  321        * 
  322        * @exception IllegalArgumentException
  323        *                if the specified context path is malformed (it must be ""
  324        *                or start with a slash)
  325        * @exception IllegalStateException
  326        *                if the specified context path is already attached to an
  327        *                existing web application
  328        * @exception IOException
  329        *                if an input/output error was encountered during
  330        *                installation
  331        */
  332       public void install(String contextPath, URL war) throws IOException {
  333           Member[] members = getCluster().getMembers();
  334           Member localMember = getCluster().getLocalMember();
  335           FileMessageFactory factory = FileMessageFactory.getInstance(new File(
  336                   war.getFile()), false);
  337           FileMessage msg = new FileMessage(localMember, war.getFile(),
  338                   contextPath);
  339           if(log.isDebugEnabled())
  340               log.debug("Send cluster war deployment [ path:"
  341                       + contextPath + " war: " + war + " ] started.");
  342           msg = factory.readMessage(msg);
  343           while (msg != null) {
  344               for (int i = 0; i < members.length; i++) {
  345                   if (log.isDebugEnabled())
  346                       log.debug("Send cluster war fragment [ path: "
  347                               + contextPath + " war: " + war + " to: " +  members[i] + " ]");
  348                   getCluster().send(msg, members[i]);
  349               } //for
  350               msg = factory.readMessage(msg);
  351           } //while
  352           if(log.isDebugEnabled())
  353               log.debug("Send cluster war deployment [ path: "
  354                       + contextPath + " war: " + war + " ] finished.");
  355       }
  356   
  357       /**
  358        * Remove an existing web application, attached to the specified context
  359        * path. If this application is successfully removed, a ContainerEvent of
  360        * type <code>REMOVE_EVENT</code> will be sent to all registered
  361        * listeners, with the removed <code>Context</code> as an argument.
  362        * Deletes the web application war file and/or directory if they exist in
  363        * the Host's appBase.
  364        * 
  365        * @param contextPath
  366        *            The context path of the application to be removed
  367        * @param undeploy
  368        *            boolean flag to remove web application from server
  369        * 
  370        * @exception IllegalArgumentException
  371        *                if the specified context path is malformed (it must be ""
  372        *                or start with a slash)
  373        * @exception IllegalArgumentException
  374        *                if the specified context path does not identify a
  375        *                currently installed web application
  376        * @exception IOException
  377        *                if an input/output error occurs during removal
  378        */
  379       public void remove(String contextPath, boolean undeploy) throws IOException {
  380           if (log.isInfoEnabled())
  381               log.info("Cluster wide remove of web app " + contextPath);
  382           // send to other nodes
  383           Member[] members = getCluster().getMembers();
  384           Member localMember = getCluster().getLocalMember();
  385           UndeployMessage msg = new UndeployMessage(localMember, System
  386                   .currentTimeMillis(), "Undeploy:" + contextPath + ":"
  387                   + System.currentTimeMillis(), contextPath, undeploy);
  388           if (log.isDebugEnabled())
  389               log.debug("Send cluster wide undeployment from "
  390                       + contextPath );
  391           cluster.send(msg);
  392           // remove locally
  393           if (undeploy) {
  394               try {
  395                   if (!isServiced(contextPath)) {
  396                       addServiced(contextPath);
  397                       try {
  398                           remove(contextPath);
  399                       } finally {
  400                           removeServiced(contextPath);
  401                       }
  402                   } else
  403                       log.error("Local remove from " + contextPath
  404                               + "failed, other manager has app in service!");
  405   
  406               } catch (Exception ex) {
  407                   log.error("local remove from " + contextPath + " failed", ex);
  408               }
  409           }
  410   
  411       }
  412   
  413       /*
  414        * Modifcation from watchDir war detected!
  415        * 
  416        * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileModified(java.io.File)
  417        */
  418       public void fileModified(File newWar) {
  419           try {
  420               File deployWar = new File(getDeployDir(), newWar.getName());
  421               copy(newWar, deployWar);
  422               String contextName = "/"
  423                       + deployWar.getName().substring(0,
  424                               deployWar.getName().lastIndexOf(".war"));
  425               if (log.isInfoEnabled())
  426                   log.info("Installing webapp[" + contextName + "] from "
  427                           + deployWar.getAbsolutePath());
  428               try {
  429                   remove(contextName, false);
  430               } catch (Exception x) {
  431                   log.error("No removal", x);
  432               }
  433               install(contextName, deployWar.toURL());
  434           } catch (Exception x) {
  435               log.error("Unable to install WAR file", x);
  436           }
  437       }
  438   
  439       /*
  440        * War remvoe from watchDir
  441        * 
  442        * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileRemoved(java.io.File)
  443        */
  444       public void fileRemoved(File removeWar) {
  445           try {
  446               String contextName = "/"
  447                       + removeWar.getName().substring(0,
  448                               removeWar.getName().lastIndexOf(".war"));
  449               if (log.isInfoEnabled())
  450                   log.info("Removing webapp[" + contextName + "]");
  451               remove(contextName, true);
  452           } catch (Exception x) {
  453               log.error("Unable to remove WAR file", x);
  454           }
  455       }
  456   
  457       /**
  458        * Given a context path, get the config file name.
  459        */
  460       protected String getConfigFile(String path) {
  461           String basename = null;
  462           if (path.equals("")) {
  463               basename = "ROOT";
  464           } else {
  465               basename = path.substring(1).replace('/', '#');
  466           }
  467           return (basename);
  468       }
  469   
  470       /**
  471        * Given a context path, get the config file name.
  472        */
  473       protected String getDocBase(String path) {
  474           String basename = null;
  475           if (path.equals("")) {
  476               basename = "ROOT";
  477           } else {
  478               basename = path.substring(1);
  479           }
  480           return (basename);
  481       }
  482   
  483       /**
  484        * Return a File object representing the "application root" directory for
  485        * our associated Host.
  486        */
  487       protected File getAppBase() {
  488   
  489           if (appBase != null) {
  490               return appBase;
  491           }
  492   
  493           File file = new File(host.getAppBase());
  494           if (!file.isAbsolute())
  495               file = new File(System.getProperty("catalina.base"), host
  496                       .getAppBase());
  497           try {
  498               appBase = file.getCanonicalFile();
  499           } catch (IOException e) {
  500               appBase = file;
  501           }
  502           return (appBase);
  503   
  504       }
  505   
  506       /**
  507        * Invoke the remove method on the deployer.
  508        */
  509       protected void remove(String path) throws Exception {
  510           // TODO Handle remove also work dir content !
  511           // Stop the context first to be nicer
  512           Context context = (Context) host.findChild(path);
  513           if (context != null) {
  514               if(log.isDebugEnabled())
  515                   log.debug("Undeploy local context " +path );
  516               ((Lifecycle) context).stop();
  517               File war = new File(getAppBase(), getDocBase(path) + ".war");
  518               File dir = new File(getAppBase(), getDocBase(path));
  519               File xml = new File(configBase, getConfigFile(path) + ".xml");
  520               if (war.exists()) {
  521                   war.delete();
  522               } else if (dir.exists()) {
  523                   undeployDir(dir);
  524               } else {
  525                   xml.delete();
  526               }
  527               // Perform new deployment and remove internal HostConfig state
  528               check(path);
  529           }
  530   
  531       }
  532   
  533       /**
  534        * Delete the specified directory, including all of its contents and
  535        * subdirectories recursively.
  536        * 
  537        * @param dir
  538        *            File object representing the directory to be deleted
  539        */
  540       protected void undeployDir(File dir) {
  541   
  542           String files[] = dir.list();
  543           if (files == null) {
  544               files = new String[0];
  545           }
  546           for (int i = 0; i < files.length; i++) {
  547               File file = new File(dir, files[i]);
  548               if (file.isDirectory()) {
  549                   undeployDir(file);
  550               } else {
  551                   file.delete();
  552               }
  553           }
  554           dir.delete();
  555   
  556       }
  557   
  558       /*
  559        * Call watcher to check for deploy changes
  560        * 
  561        * @see org.apache.catalina.cluster.ClusterDeployer#backgroundProcess()
  562        */
  563       public void backgroundProcess() {
  564           if (started) {
  565               count = (count + 1) % processDeployFrequency;
  566               if (count == 0 && watchEnabled) {
  567                   watcher.check();
  568               }
  569           }
  570   
  571       }
  572   
  573       /*--Deployer Operations ------------------------------------*/
  574   
  575       /**
  576        * Invoke the check method on the deployer.
  577        */
  578       protected void check(String name) throws Exception {
  579           String[] params = { name };
  580           String[] signature = { "java.lang.String" };
  581           mBeanServer.invoke(oname, "check", params, signature);
  582       }
  583   
  584       /**
  585        * Invoke the check method on the deployer.
  586        */
  587       protected boolean isServiced(String name) throws Exception {
  588           String[] params = { name };
  589           String[] signature = { "java.lang.String" };
  590           Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced",
  591                   params, signature);
  592           return result.booleanValue();
  593       }
  594   
  595       /**
  596        * Invoke the check method on the deployer.
  597        */
  598       protected void addServiced(String name) throws Exception {
  599           String[] params = { name };
  600           String[] signature = { "java.lang.String" };
  601           mBeanServer.invoke(oname, "addServiced", params, signature);
  602       }
  603   
  604       /**
  605        * Invoke the check method on the deployer.
  606        */
  607       protected void removeServiced(String name) throws Exception {
  608           String[] params = { name };
  609           String[] signature = { "java.lang.String" };
  610           mBeanServer.invoke(oname, "removeServiced", params, signature);
  611       }
  612   
  613       /*--Instance Getters/Setters--------------------------------*/
  614       public CatalinaCluster getCluster() {
  615           return cluster;
  616       }
  617   
  618       public void setCluster(CatalinaCluster cluster) {
  619           this.cluster = cluster;
  620       }
  621   
  622       public boolean equals(Object listener) {
  623           return super.equals(listener);
  624       }
  625   
  626       public int hashCode() {
  627           return super.hashCode();
  628       }
  629   
  630       public String getDeployDir() {
  631           return deployDir;
  632       }
  633   
  634       public void setDeployDir(String deployDir) {
  635           this.deployDir = deployDir;
  636       }
  637   
  638       public String getTempDir() {
  639           return tempDir;
  640       }
  641   
  642       public void setTempDir(String tempDir) {
  643           this.tempDir = tempDir;
  644       }
  645   
  646       public String getWatchDir() {
  647           return watchDir;
  648       }
  649   
  650       public void setWatchDir(String watchDir) {
  651           this.watchDir = watchDir;
  652       }
  653   
  654       public boolean isWatchEnabled() {
  655           return watchEnabled;
  656       }
  657   
  658       public boolean getWatchEnabled() {
  659           return watchEnabled;
  660       }
  661   
  662       public void setWatchEnabled(boolean watchEnabled) {
  663           this.watchEnabled = watchEnabled;
  664       }
  665   
  666       /**
  667        * Return the frequency of watcher checks.
  668        */
  669       public int getProcessDeployFrequency() {
  670   
  671           return (this.processDeployFrequency);
  672   
  673       }
  674   
  675       /**
  676        * Set the watcher checks frequency.
  677        * 
  678        * @param processExpiresFrequency
  679        *            the new manager checks frequency
  680        */
  681       public void setProcessDeployFrequency(int processExpiresFrequency) {
  682   
  683           if (processExpiresFrequency <= 0) {
  684               return;
  685           }
  686           this.processDeployFrequency = processExpiresFrequency;
  687       }
  688   
  689       /**
  690        * Copy a file to the specified temp directory. This is required only
  691        * because Jasper depends on it.
  692        */
  693       protected boolean copy(File from, File to) {
  694           try {
  695               if (!to.exists())
  696                   to.createNewFile();
  697               java.io.FileInputStream is = new java.io.FileInputStream(from);
  698               java.io.FileOutputStream os = new java.io.FileOutputStream(to,
  699                       false);
  700               byte[] buf = new byte[4096];
  701               while (true) {
  702                   int len = is.read(buf);
  703                   if (len < 0)
  704                       break;
  705                   os.write(buf, 0, len);
  706               }
  707               is.close();
  708               os.close();
  709           } catch (IOException e) {
  710               log.error("Unable to copy file from:" + from + " to:" + to, e);
  711               return false;
  712           }
  713           return true;
  714       }
  715   
  716   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » cluster » deploy » [javadoc | source]