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