View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.deploy;
15  
16  import java.io.File;
17  import java.io.FilenameFilter;
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import org.eclipse.jetty.deploy.providers.MonitoredDirAppProvider;
22  import org.eclipse.jetty.server.Server;
23  import org.eclipse.jetty.server.handler.ContextHandler;
24  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
25  import org.eclipse.jetty.util.AttributesMap;
26  import org.eclipse.jetty.util.Scanner;
27  import org.eclipse.jetty.util.component.AbstractLifeCycle;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.resource.Resource;
30  import org.eclipse.jetty.xml.XmlConfiguration;
31  
32  /**
33   * Legacy Context Deployer.
34   * 
35   * <p>
36   * Note: The WebAppDeployer is being phased out of Jetty in favor of the {@link DeploymentManager} and
37   * {@link org.eclipse.jetty.deploy.providers.ContextProvider} implementation.
38   * 
39   * <p>
40   * This deployer scans a designated directory by {@link #setConfigurationDir(String)} for the appearance/disappearance
41   * or changes to xml configuration files. The scan is performed at startup and at an optional hot deployment frequency
42   * specified by {@link #setScanInterval(int)}. By default, the scanning is NOT recursive, but can be made so by
43   * {@link #setRecursive(boolean)}.
44   * 
45   * <p>
46   * Each configuration file is in {@link XmlConfiguration} format and represents the configuration of a instance of
47   * {@link ContextHandler} (or a subclass specified by the XML <code>Configure</code> element).
48   * 
49   * <p>
50   * The xml should configure the context and the instance is deployed to the {@link ContextHandlerCollection} specified
51   * by {@link Server#setHandler(org.eclipse.jetty.server.Handler)}.
52   * 
53   * <p>
54   * Similarly, when one of these existing files is removed, the corresponding context is undeployed; when one of these
55   * files is changed, the corresponding context is undeployed, the (changed) xml config file reapplied to it, and then
56   * (re)deployed.
57   * 
58   * <p>
59   * Note that the context itself is NOT copied into the hot deploy directory. The webapp directory or war file can exist
60   * anywhere. It is the xml config file that points to it's location and deploys it from there.
61   * 
62   * <p>
63   * It means, for example, that you can keep a "read-only" copy of your webapp somewhere, and apply different
64   * configurations to it simply by dropping different xml configuration files into the configuration directory.
65   * 
66   * @see DeploymentManager
67   * @see MonitoredDirAppProvider
68   * 
69   * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer to watch a directory for changes at
70   *                         a configurable interval."
71   */
72  @SuppressWarnings("unchecked")
73  public class ContextDeployer extends AbstractLifeCycle
74  {
75      private int _scanInterval=10;
76      private Scanner _scanner;
77      private ScannerListener _scannerListener;
78      private Resource _contextsDir;
79      private Map _currentDeployments = new HashMap();
80      private ContextHandlerCollection _contexts;
81      private ConfigurationManager _configMgr;
82      private boolean _recursive = false;
83      private AttributesMap _contextAttributes = new AttributesMap();
84      
85      /* ------------------------------------------------------------ */
86      protected class ScannerListener implements Scanner.DiscreteListener
87      {
88          /**
89           * Handle a new deployment
90           * 
91           * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileAdded(java.lang.String)
92           */
93          public void fileAdded(String filename) throws Exception
94          {
95              deploy(filename);
96          }
97  
98          /**
99           * Handle a change to an existing deployment. Undeploy then redeploy.
100          * 
101          * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileChanged(java.lang.String)
102          */
103         public void fileChanged(String filename) throws Exception
104         {
105             redeploy(filename);
106         }
107 
108         /**
109          * Handle an undeploy.
110          * 
111          * @see org.eclipse.jetty.util.Scanner.DiscreteListener#fileRemoved(java.lang.String)
112          */
113         public void fileRemoved(String filename) throws Exception
114         {
115             undeploy(filename);
116         }
117         @Override
118         public String toString()
119         {
120             return "ContextDeployer$Scanner";
121         }
122     }
123 
124     /**
125      * Constructor
126      */
127     public ContextDeployer() 
128     {
129         _scanner=new Scanner();
130     }
131 
132     /* ------------------------------------------------------------ */
133     /**
134      * @return the ContextHandlerColletion to which to deploy the contexts
135      */
136     public ContextHandlerCollection getContexts()
137     {
138         return _contexts;
139     }
140 
141     /* ------------------------------------------------------------ */
142     /**
143      * Associate with a {@link ContextHandlerCollection}.
144      * 
145      * @param contexts
146      *            the ContextHandlerColletion to which to deploy the contexts
147      */
148     public void setContexts(ContextHandlerCollection contexts)
149     {
150         if (isStarted()||isStarting())
151             throw new IllegalStateException("Cannot set Contexts after deployer start");
152         _contexts=contexts;
153     }
154 
155     /* ------------------------------------------------------------ */
156     /**
157      * @param seconds
158      *            The period in second between scans for changed configuration
159      *            files. A zero or negative interval disables hot deployment
160      */
161     public void setScanInterval(int seconds)
162     {
163         if (isStarted()||isStarting())
164             throw new IllegalStateException("Cannot change scan interval after deployer start");
165         _scanInterval=seconds;
166     }
167 
168     /* ------------------------------------------------------------ */
169     public int getScanInterval()
170     {
171         return _scanInterval;
172     }
173 
174     /* ------------------------------------------------------------ */
175     /**
176      * @param dir Directory to scan for context descriptors
177      */
178     public void setContextsDir(String dir)
179     {
180         try
181         {
182             _contextsDir=Resource.newResource(dir);
183         }
184         catch(Exception e)
185         {
186             throw new IllegalArgumentException(e);
187         }
188     }
189 
190     /* ------------------------------------------------------------ */
191     public String getContextsDir()
192     {
193         return _contextsDir==null?null:_contextsDir.toString();
194     }
195 
196     /* ------------------------------------------------------------ */
197     /**
198      * @param dir
199      * @throws Exception
200      * @deprecated use {@link #setContextsDir(String)}
201      */
202     @Deprecated
203     public void setConfigurationDir(String dir) throws Exception
204     {
205         setConfigurationDir(Resource.newResource(dir));
206     }
207 
208     /* ------------------------------------------------------------ */
209     /**
210      * @param file
211      * @throws Exception
212      * @deprecated use {@link #setContextsDir(String)}
213      */
214     @Deprecated
215     public void setConfigurationDir(File file) throws Exception
216     {
217         setConfigurationDir(Resource.newResource(file.toURL()));
218     }
219 
220     /* ------------------------------------------------------------ */
221     /**
222      * @param resource
223      * @deprecated use {@link #setContextsDir(String)}
224      */
225     @Deprecated
226     public void setConfigurationDir(Resource resource)
227     {
228         if (isStarted()||isStarting())
229             throw new IllegalStateException("Cannot change hot deploy dir after deployer start");
230         _contextsDir=resource;
231     }
232 
233     /* ------------------------------------------------------------ */
234     /**
235      * @param directory
236      * @deprecated use {@link #setContextsDir(String)}
237      */
238     @Deprecated
239     public void setDirectory(String directory) throws Exception
240     {
241         setConfigurationDir(directory);
242     }
243     
244     /* ------------------------------------------------------------ */
245     /**
246      * @return the directory
247      * @deprecated use {@link #setContextsDir(String)}
248      */
249     @Deprecated
250     public String getDirectory()
251     {
252         return getConfigurationDir().getName();
253     }
254 
255     /* ------------------------------------------------------------ */
256     /**
257      * @return the configuration directory
258      * @deprecated use {@link #setContextsDir(String)}
259      */
260     @Deprecated
261     public Resource getConfigurationDir()
262     {
263         return _contextsDir;
264     }
265 
266     /* ------------------------------------------------------------ */
267     /**
268      * @param configMgr
269      */
270     public void setConfigurationManager(ConfigurationManager configMgr)
271     {
272         _configMgr=configMgr;
273     }
274 
275     /* ------------------------------------------------------------ */
276     /**
277      * @return the configuration manager
278      */
279     public ConfigurationManager getConfigurationManager()
280     {
281         return _configMgr;
282     }
283 
284 
285     /* ------------------------------------------------------------ */
286     public void setRecursive (boolean recursive)
287     {
288         _recursive=recursive;
289     }
290 
291     /* ------------------------------------------------------------ */
292     public boolean getRecursive ()
293     {
294         return _recursive;
295     }
296 
297     /* ------------------------------------------------------------ */
298     public boolean isRecursive()
299     {
300         return _recursive;
301     }
302     
303 
304     /* ------------------------------------------------------------ */
305     /**
306      * Set a contextAttribute that will be set for every Context deployed by this deployer.
307      * @param name
308      * @param value
309      */
310     public void setAttribute (String name, Object value)
311     {
312         _contextAttributes.setAttribute(name,value);
313     }
314     
315 
316     /* ------------------------------------------------------------ */
317     /**
318      * Get a contextAttribute that will be set for every Context deployed by this deployer.
319      * @param name
320      * @return the attribute value
321      */
322     public Object getAttribute (String name)
323     {
324         return _contextAttributes.getAttribute(name);
325     }
326     
327 
328     /* ------------------------------------------------------------ */
329     /**
330      * Remove a contextAttribute that will be set for every Context deployed by this deployer.
331      * @param name
332      */
333     public void removeAttribute(String name)
334     {
335         _contextAttributes.removeAttribute(name);
336     }
337 
338     /* ------------------------------------------------------------ */
339     private void deploy(String filename) throws Exception
340     {
341         ContextHandler context=createContext(filename);
342         Log.info("Deploy "+filename+" -> "+ context);
343         _contexts.addHandler(context);
344         _currentDeployments.put(filename,context);
345         if (_contexts.isStarted())
346             context.start();
347     }
348 
349     /* ------------------------------------------------------------ */
350     private void undeploy(String filename) throws Exception
351     {
352         ContextHandler context=(ContextHandler)_currentDeployments.get(filename);
353         Log.info("Undeploy "+filename+" -> "+context);
354         if (context==null)
355             return;
356         context.stop();
357         _contexts.removeHandler(context);
358         _currentDeployments.remove(filename);
359     }
360 
361     /* ------------------------------------------------------------ */
362     private void redeploy(String filename) throws Exception
363     {
364         undeploy(filename);
365         deploy(filename);
366     }
367 
368     /* ------------------------------------------------------------ */
369     /**
370      * Start the hot deployer looking for webapps to deploy/undeploy
371      * 
372      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
373      */
374     @SuppressWarnings("deprecation")
375     @Override
376     protected void doStart() throws Exception
377     {
378         if (_contextsDir==null)
379             throw new IllegalStateException("No configuration dir specified");
380 
381         if (_contexts==null)
382             throw new IllegalStateException("No context handler collection specified for deployer");
383 
384         _scanner.setScanDir(_contextsDir.getFile());
385         _scanner.setScanInterval(getScanInterval());
386         _scanner.setRecursive(_recursive); //only look in the top level for deployment files?
387         // Accept changes only in files that could be a deployment descriptor
388         _scanner.setFilenameFilter(new FilenameFilter()
389         {
390             public boolean accept(File dir, String name)
391             {
392                 try
393                 {
394                     if (name.endsWith(".xml"))
395                         return true;
396                     return false;
397                 }
398                 catch (Exception e)
399                 {
400                     Log.warn(e);
401                     return false;
402                 }
403             }
404         });
405         _scannerListener=new ScannerListener();
406         _scanner.addListener(_scannerListener);
407         _scanner.scan();
408         _scanner.start();
409         _contexts.getServer().getContainer().addBean(_scanner);
410     }
411 
412     /* ------------------------------------------------------------ */
413     /**
414      * Stop the hot deployer.
415      * 
416      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
417      */
418     @Override
419     protected void doStop() throws Exception
420     {
421         _scanner.removeListener(_scannerListener);
422         _scanner.stop();
423     }
424 
425     /* ------------------------------------------------------------ */
426     /**
427      * Create a WebAppContext for the webapp being hot deployed, then apply the
428      * xml config file to it to configure it.
429      * 
430      * @param filename
431      *            the config file found in the hot deploy directory
432      * @return
433      * @throws Exception
434      */
435     private ContextHandler createContext(String filename) throws Exception
436     {
437         // The config file can call any method on WebAppContext to configure
438         // the webapp being deployed.
439         Resource resource = Resource.newResource(filename);
440         if (!resource.exists())
441             return null;
442 
443         XmlConfiguration xmlConfiguration=new XmlConfiguration(resource.getURL());
444         HashMap properties = new HashMap();
445         properties.put("Server", _contexts.getServer());
446         if (_configMgr!=null)
447             properties.putAll(_configMgr.getProperties());
448            
449         xmlConfiguration.setProperties(properties);
450         ContextHandler context=(ContextHandler)xmlConfiguration.configure();
451         context.setAttributes(new AttributesMap(_contextAttributes));
452         return context;
453     }
454 
455 }