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.io.IOException;
19  import java.util.HashMap;
20  import java.util.Map;
21  
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   * Context Deployer
34   * 
35   * This deployer scans a designated directory by
36   * {@link #setConfigurationDir(String)} for the appearance/disappearance or
37   * changes to xml configuration files. The scan is performed at startup and at
38   * an optional hot deployment frequency specified by
39   * {@link #setScanInterval(int)}. By default, the scanning is NOT recursive,
40   * but can be made so by {@link #setRecursive(boolean)}.
41   * 
42   * Each configuration file is in {@link XmlConfiguration} format and represents
43   * the configuration of a instance of {@link ContextHandler} (or a subclass
44   * specified by the XML <code>Configure</code> element).
45   * 
46   * The xml should configure the context and the instance is deployed to the
47   * {@link ContextHandlerCollection} specified by {@link #setContexts(Server)}.
48   * 
49   * Similarly, when one of these existing files is removed, the corresponding
50   * context is undeployed; when one of these files is changed, the corresponding
51   * context is undeployed, the (changed) xml config file reapplied to it, and
52   * then (re)deployed.
53   * 
54   * Note that the context itself is NOT copied into the hot deploy directory. The
55   * webapp directory or war file can exist anywhere. It is the xml config file
56   * that points to it's location and deploys it from there.
57   * 
58   * It means, for example, that you can keep a "read-only" copy of your webapp
59   * somewhere, and apply different configurations to it simply by dropping
60   * different xml configuration files into the configuration directory.
61   * 
62   * @org.apache.xbean.XBean element="hotDeployer" description="Creates a hot deployer 
63   * 						to watch a directory for changes at a configurable interval."
64   * 
65   */
66  public class ContextDeployer extends AbstractLifeCycle
67  {
68      public final static String NAME="ConfiguredDeployer";
69      private int _scanInterval=10;
70      private Scanner _scanner;
71      private ScannerListener _scannerListener;
72      private Resource _configurationDir;
73      private Map _currentDeployments=new HashMap();
74      private ContextHandlerCollection _contexts;
75      private ConfigurationManager _configMgr;
76      private boolean _recursive = false;
77      private AttributesMap _contextAttributes = new AttributesMap();
78      
79      /* ------------------------------------------------------------ */
80      protected class ScannerListener implements Scanner.DiscreteListener
81      {
82          /**
83           * Handle a new deployment
84           * 
85           * @see org.eclipse.jetty.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.eclipse.jetty.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.eclipse.jetty.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     
262     /**
263      * Set a contextAttribute that will be set for every Context deployed by this deployer.
264      * @param name
265      * @param value
266      */
267     public void setAttribute (String name, Object value)
268     {
269         _contextAttributes.setAttribute(name,value);
270     }
271     
272     
273     /**
274      * Get a contextAttribute that will be set for every Context deployed by this deployer.
275      * @param name
276      * @return
277      */
278     public Object getAttribute (String name)
279     {
280         return _contextAttributes.getAttribute(name);
281     }
282     
283     
284     /**
285      * Remove a contextAttribute that will be set for every Context deployed by this deployer.
286      * @param name
287      */
288     public void removeAttribute(String name)
289     {
290         _contextAttributes.removeAttribute(name);
291     }
292 
293     /* ------------------------------------------------------------ */
294     private void deploy(String filename) throws Exception
295     {
296         ContextHandler context=createContext(filename);
297         Log.info("Deploy "+filename+" -> "+ context);
298         _contexts.addHandler(context);
299         _currentDeployments.put(filename,context);
300         if (_contexts.isStarted())
301             context.start();
302     }
303 
304     /* ------------------------------------------------------------ */
305     private void undeploy(String filename) throws Exception
306     {
307         ContextHandler context=(ContextHandler)_currentDeployments.get(filename);
308         Log.info("Undeploy "+filename+" -> "+context);
309         if (context==null)
310             return;
311         context.stop();
312         _contexts.removeHandler(context);
313         _currentDeployments.remove(filename);
314     }
315 
316     /* ------------------------------------------------------------ */
317     private void redeploy(String filename) throws Exception
318     {
319         undeploy(filename);
320         deploy(filename);
321     }
322 
323     /* ------------------------------------------------------------ */
324     /**
325      * Start the hot deployer looking for webapps to deploy/undeploy
326      * 
327      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
328      */
329     protected void doStart() throws Exception
330     {
331         if (_configurationDir==null)
332             throw new IllegalStateException("No configuration dir specified");
333 
334         if (_contexts==null)
335             throw new IllegalStateException("No context handler collection specified for deployer");
336 
337         _scanner.setScanDir(_configurationDir.getFile());
338         _scanner.setScanInterval(getScanInterval());
339         _scanner.setRecursive(_recursive); //only look in the top level for deployment files?
340         // Accept changes only in files that could be a deployment descriptor
341         _scanner.setFilenameFilter(new FilenameFilter()
342         {
343             public boolean accept(File dir, String name)
344             {
345                 try
346                 {
347                     if (name.endsWith(".xml")&&dir.equals(getConfigurationDir().getFile()))
348                         return true;
349                     return false;
350                 }
351                 catch (IOException e)
352                 {
353                     Log.warn(e);
354                     return false;
355                 }
356             }
357         });
358         _scannerListener=new ScannerListener();
359         _scanner.addListener(_scannerListener);
360         _scanner.scan();
361         _scanner.start();
362         _contexts.getServer().getContainer().addBean(_scanner);
363     }
364 
365     /* ------------------------------------------------------------ */
366     /**
367      * Stop the hot deployer.
368      * 
369      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
370      */
371     protected void doStop() throws Exception
372     {
373         _scanner.removeListener(_scannerListener);
374         _scanner.stop();
375     }
376 
377     /* ------------------------------------------------------------ */
378     /**
379      * Create a WebAppContext for the webapp being hot deployed, then apply the
380      * xml config file to it to configure it.
381      * 
382      * @param filename
383      *            the config file found in the hot deploy directory
384      * @return
385      * @throws Exception
386      */
387     private ContextHandler createContext(String filename) throws Exception
388     {
389         // The config file can call any method on WebAppContext to configure
390         // the webapp being deployed.
391         Resource resource = Resource.newResource(filename);
392         if (!resource.exists())
393             return null;
394 
395         XmlConfiguration xmlConfiguration=new XmlConfiguration(resource.getURL());
396         HashMap properties = new HashMap();
397         properties.put("Server", _contexts.getServer());
398         if (_configMgr!=null)
399             properties.putAll(_configMgr.getProperties());
400            
401         xmlConfiguration.setProperties(properties);
402         ContextHandler context=(ContextHandler)xmlConfiguration.configure();
403         context.setAttributes(new AttributesMap(_contextAttributes));
404         return context;
405     }
406 
407 }