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