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

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