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