View Javadoc

    1   //========================================================================
    2   //$Id: HotDeployer.java 1113 2006-10-20 11:40:15Z janb $
    3   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
    4   //------------------------------------------------------------------------
    5   //Licensed under the Apache License, Version 2.0 (the "License");
    6   //you may not use this file except in compliance with the License.
    7   //You may obtain a copy of the License at
    8   //http://www.apache.org/licenses/LICENSE-2.0
    9   //Unless required by applicable law or agreed to in writing, software
    10  //distributed under the License is distributed on an "AS IS" BASIS,
    11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //See the License for the specific language governing permissions and
    13  //limitations under the License.
    14  //========================================================================
    15
    16  package org.mortbay.jetty.deployer;
    17
    18  import java.io.File;
    19  import java.io.FilenameFilter;
    20  import java.io.IOException;
    21  import java.util.HashMap;
    22  import java.util.Map;
    23
    24  import org.mortbay.component.AbstractLifeCycle;
    25  import org.mortbay.jetty.Server;
    26  import org.mortbay.jetty.handler.ContextHandler;
    27  import org.mortbay.jetty.handler.ContextHandlerCollection;
    28  import org.mortbay.log.Log;
    29  import org.mortbay.resource.Resource;
    30  import org.mortbay.util.Scanner;
    31  import org.mortbay.xml.XmlConfiguration;
    32
    33  /**
    34   * Context Deployer
    35   *
    36   * This deployer scans a designated directory by
    37   * {@link #setConfigurationDir(String)} for the appearance/disappearance or
    38   * changes to xml configuration files. The scan is performed at startup and at
    39   * an optional hot deployment frequency specified by
    40   * {@link #setScanInterval(int)}. By default, the scanning is NOT recursive,
    41   * but can be made so by {@link #setRecursive(boolean)}.
    42   *
    43   * Each configuration file is in {@link XmlConfiguration} format and represents
    44   * the configuration of a instance of {@link ContextHandler} (or a subclass
    45   * specified by the XML <code>Configure</code> element).
    46   *
    47   * The xml should configure the context and the instance is deployed to the
    48   * {@link ContextHandlerCollection} specified by {@link #setContexts(Server)}.
    49   *
    50   * Similarly, when one of these existing files is removed, the corresponding
    51   * context is undeployed; when one of these files is changed, the corresponding
    52   * context is undeployed, the (changed) xml config file reapplied to it, and
    53   * then (re)deployed.
    54   *
    55   * Note that the context itself is NOT copied into the hot deploy directory. The
    56   * webapp directory or war file can exist anywhere. It is the xml config file
    57   * that points to it's location and deploys it from there.
    58   *
    59   * It means, for example, that you can keep a "read-only" copy of your webapp
    60   * somewhere, and apply different configurations to it simply by dropping
    61   * different xml configuration files into the configuration directory.
    62   *
    63   * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer
    64   * 						to watch a directory for changes at a configurable interval."
    65   *
    66   */
    67  public class ContextDeployer extends AbstractLifeCycle
    68  {
    69      public final static String NAME="ConfiguredDeployer";
    70      private int _scanInterval=10;
    71      private Scanner _scanner;
    72      private ScannerListener _scannerListener;
    73      private Resource _configurationDir;
    74      private Map _currentDeployments=new HashMap();
    75      private ContextHandlerCollection _contexts;
    76      private ConfigurationManager _configMgr;
    77      private boolean _recursive = false;
    78
    79      /* ------------------------------------------------------------ */
    80      protected class ScannerListener implements Scanner.DiscreteListener
    81      {
    82          /**
    83           * Handle a new deployment
    84           *
    85           * @see org.mortbay.util.Scanner.FileAddedListener#fileAdded(java.lang.String)
    86           */
    87          public void fileAdded(String filename) throws Exception
    88          {
    89              deploy(filename);
    90          }
    91
    92          /**
    93           * Handle a change to an existing deployment. Undeploy then redeploy.
    94           *
    95           * @see org.mortbay.util.Scanner.FileChangedListener#fileChanged(java.lang.String)
    96           */
    97          public void fileChanged(String filename) throws Exception
    98          {
    99              redeploy(filename);
    100         }
    101
    102         /**
    103          * Handle an undeploy.
    104          *
    105          * @see org.mortbay.util.Scanner.FileRemovedListener#fileRemoved(java.lang.String)
    106          */
    107         public void fileRemoved(String filename) throws Exception
    108         {
    109             undeploy(filename);
    110         }
    111         public String toString()
    112         {
    113             return "ContextDeployer$Scanner";
    114         }
    115     }
    116
    117     /**
    118      * Constructor
    119      *
    120      * @throws Exception
    121      */
    122     public ContextDeployer() throws Exception
    123     {
    124         _scanner=new Scanner();
    125     }
    126
    127     /* ------------------------------------------------------------ */
    128     /**
    129      * @return the ContextHandlerColletion to which to deploy the contexts
    130      */
    131     public ContextHandlerCollection getContexts()
    132     {
    133         return _contexts;
    134     }
    135
    136     /* ------------------------------------------------------------ */
    137     /**
    138      * Associate with a {@link ContextHandlerCollection}.
    139      *
    140      * @param contexts
    141      *            the ContextHandlerColletion to which to deploy the contexts
    142      */
    143     public void setContexts(ContextHandlerCollection contexts)
    144     {
    145         if (isStarted()||isStarting())
    146             throw new IllegalStateException("Cannot set Contexts after deployer start");
    147         _contexts=contexts;
    148     }
    149
    150     /* ------------------------------------------------------------ */
    151     /**
    152      * @param seconds
    153      *            The period in second between scans for changed configuration
    154      *            files. A zero or negative interval disables hot deployment
    155      */
    156     public void setScanInterval(int seconds)
    157     {
    158         if (isStarted()||isStarting())
    159             throw new IllegalStateException("Cannot change scan interval after deployer start");
    160         _scanInterval=seconds;
    161     }
    162
    163     /* ------------------------------------------------------------ */
    164     public int getScanInterval()
    165     {
    166         return _scanInterval;
    167     }
    168
    169     /* ------------------------------------------------------------ */
    170     /**
    171      * @param dir
    172      * @throws Exception
    173      */
    174     public void setConfigurationDir(String dir) throws Exception
    175     {
    176         setConfigurationDir(Resource.newResource(dir));
    177     }
    178
    179     /* ------------------------------------------------------------ */
    180     /**
    181      * @param file
    182      * @throws Exception
    183      */
    184     public void setConfigurationDir(File file) throws Exception
    185     {
    186         setConfigurationDir(Resource.newResource(file.toURL()));
    187     }
    188
    189     /* ------------------------------------------------------------ */
    190     /**
    191      * @param resource
    192      */
    193     public void setConfigurationDir(Resource resource)
    194     {
    195         if (isStarted()||isStarting())
    196             throw new IllegalStateException("Cannot change hot deploy dir after deployer start");
    197         _configurationDir=resource;
    198     }
    199
    200     /* ------------------------------------------------------------ */
    201     /**
    202      * @param directory
    203      */
    204     public void setDirectory(String directory) throws Exception
    205     {
    206 		setConfigurationDir(directory);
    207     }
    208
    209     /* ------------------------------------------------------------ */
    210     /**
    211      * @return
    212      */
    213     public String getDirectory()
    214     {
    215         return getConfigurationDir().getName();
    216     }
    217
    218     /* ------------------------------------------------------------ */
    219     /**
    220      * @return
    221      */
    222     public Resource getConfigurationDir()
    223     {
    224         return _configurationDir;
    225     }
    226
    227     /* ------------------------------------------------------------ */
    228     /**
    229      * @param configMgr
    230      */
    231     public void setConfigurationManager(ConfigurationManager configMgr)
    232     {
    233         _configMgr=configMgr;
    234     }
    235
    236     /* ------------------------------------------------------------ */
    237     /**
    238      * @return
    239      */
    240     public ConfigurationManager getConfigurationManager()
    241     {
    242         return _configMgr;
    243     }
    244
    245
    246     public void setRecursive (boolean recursive)
    247     {
    248         _recursive=recursive;
    249     }
    250
    251     public boolean getRecursive ()
    252     {
    253         return _recursive;
    254     }
    255
    256     public boolean isRecursive()
    257     {
    258         return _recursive;
    259     }
    260     /* ------------------------------------------------------------ */
    261     private void deploy(String filename) throws Exception
    262     {
    263         ContextHandler context=createContext(filename);
    264         Log.info("Deploy "+filename+" -> "+ context);
    265         _contexts.addHandler(context);
    266         _currentDeployments.put(filename,context);
    267         if (_contexts.isStarted())
    268             context.start();
    269     }
    270
    271     /* ------------------------------------------------------------ */
    272     private void undeploy(String filename) throws Exception
    273     {
    274         ContextHandler context=(ContextHandler)_currentDeployments.get(filename);
    275         Log.info("Undeploy "+filename+" -> "+context);
    276         if (context==null)
    277             return;
    278         context.stop();
    279         _contexts.removeHandler(context);
    280         _currentDeployments.remove(filename);
    281     }
    282
    283     /* ------------------------------------------------------------ */
    284     private void redeploy(String filename) throws Exception
    285     {
    286         undeploy(filename);
    287         deploy(filename);
    288     }
    289
    290     /* ------------------------------------------------------------ */
    291     /**
    292      * Start the hot deployer looking for webapps to deploy/undeploy
    293      *
    294      * @see org.mortbay.component.AbstractLifeCycle#doStart()
    295      */
    296     protected void doStart() throws Exception
    297     {
    298         if (_configurationDir==null)
    299             throw new IllegalStateException("No configuraition dir specified");
    300
    301         if (_contexts==null)
    302             throw new IllegalStateException("No context handler collection specified for deployer");
    303
    304         _scanner.setScanDir(_configurationDir.getFile());
    305         _scanner.setScanInterval(getScanInterval());
    306         _scanner.setRecursive(_recursive); //only look in the top level for deployment files?
    307         // Accept changes only in files that could be a deployment descriptor
    308         _scanner.setFilenameFilter(new FilenameFilter()
    309         {
    310             public boolean accept(File dir, String name)
    311             {
    312                 try
    313                 {
    314                     if (name.endsWith(".xml"))
    315                         return true;
    316                     return false;
    317                 }
    318                 catch (Exception e)
    319                 {
    320                     Log.warn(e);
    321                     return false;
    322                 }
    323             }
    324         });
    325         _scannerListener=new ScannerListener();
    326         _scanner.addListener(_scannerListener);
    327         _scanner.scan();
    328         _scanner.start();
    329         _contexts.getServer().getContainer().addBean(_scanner);
    330     }
    331
    332     /* ------------------------------------------------------------ */
    333     /**
    334      * Stop the hot deployer.
    335      *
    336      * @see org.mortbay.component.AbstractLifeCycle#doStop()
    337      */
    338     protected void doStop() throws Exception
    339     {
    340         _scanner.removeListener(_scannerListener);
    341         _scanner.stop();
    342     }
    343
    344     /* ------------------------------------------------------------ */
    345     /**
    346      * Create a WebAppContext for the webapp being hot deployed, then apply the
    347      * xml config file to it to configure it.
    348      *
    349      * @param filename
    350      *            the config file found in the hot deploy directory
    351      * @return
    352      * @throws Exception
    353      */
    354     private ContextHandler createContext(String filename) throws Exception
    355     {
    356         // The config file can call any method on WebAppContext to configure
    357         // the webapp being deployed.
    358         Resource resource = Resource.newResource(filename);
    359         if (!resource.exists())
    360             return null;
    361
    362         XmlConfiguration xmlConfiguration=new XmlConfiguration(resource.getURL());
    363         HashMap properties = new HashMap();
    364         properties.put("Server", _contexts.getServer());
    365         if (_configMgr!=null)
    366             properties.putAll(_configMgr.getProperties());
    367
    368         xmlConfiguration.setProperties(properties);
    369         ContextHandler context=(ContextHandler)xmlConfiguration.configure();
    370         return context;
    371     }
    372
    373 }