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