View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.overlays;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.InetAddress;
24  import java.net.MalformedURLException;
25  import java.net.URI;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.net.UnknownHostException;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Properties;
38  import java.util.Set;
39  import java.util.Timer;
40  import java.util.concurrent.ConcurrentHashMap;
41  import java.util.concurrent.atomic.AtomicBoolean;
42  import java.util.regex.Pattern;
43  
44  import javax.servlet.ServletContextEvent;
45  import javax.servlet.ServletContextListener;
46  
47  import org.eclipse.jetty.deploy.App;
48  import org.eclipse.jetty.deploy.AppProvider;
49  import org.eclipse.jetty.deploy.ConfigurationManager;
50  import org.eclipse.jetty.deploy.DeploymentManager;
51  import org.eclipse.jetty.jndi.java.javaRootURLContext;
52  import org.eclipse.jetty.jndi.local.localContextRoot;
53  import org.eclipse.jetty.server.ResourceCache;
54  import org.eclipse.jetty.server.Server;
55  import org.eclipse.jetty.server.handler.ContextHandler;
56  import org.eclipse.jetty.servlet.Holder;
57  import org.eclipse.jetty.servlet.ServletHandler;
58  import org.eclipse.jetty.util.IO;
59  import org.eclipse.jetty.util.Scanner;
60  import org.eclipse.jetty.util.component.AbstractLifeCycle;
61  import org.eclipse.jetty.util.log.Logger;
62  import org.eclipse.jetty.util.resource.JarResource;
63  import org.eclipse.jetty.util.resource.Resource;
64  import org.eclipse.jetty.util.resource.ResourceCollection;
65  import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
66  import org.eclipse.jetty.webapp.WebAppClassLoader;
67  import org.eclipse.jetty.webapp.WebAppContext;
68  import org.eclipse.jetty.xml.XmlConfiguration;
69  import org.xml.sax.SAXException;
70  
71  /**
72   * Overlayed AppProvider
73   * <p>
74   * This {@link AppProvider} implementation can deploy either {@link WebAppContext}s or plain
75   * {@link ContextHandler}s that are assembled from a series of overlays:
76   * <dl>
77   * <dt>webapp</dt><dd>The webapp overlay is a WAR file or docroot directory. The intent is that 
78   * the WAR should be deployed to this AppProvider unchanged from how it was delivered.  All configuration
79   * and extension should be able to be done in an overlay.</dd>
80   * <dt>template</dt><dd>A template overlay is applied to a WAR file to configure it for all instances of
81   * the webapp to be deployed in the server(s)</dd>
82   * <dt>node</dt><dd>A node overlay is applied to a template to configure it all instances of the template
83   * with node specific information (eg IP address, DB servers etc.).</dd>
84   * <dt>instance</dt><dd>An instance overlay is applied to a node and/or template to configure it 
85   * for a specific instance of the template (eg per tenant configuration).</dd>
86   * </dl>
87   * <p>
88   * Each overlays may provide the following files and subdirectories:<dl>
89   * <dt>WEB-INF/lib-overlay</dt>
90   * <dd>The lib-overlay directory can contain jars that are applied to a {@link URLClassLoader} that is
91   * available before any overlay.xml files are executed, so that classes from these jars may be used by the 
92   * overlay.xml.</dd> 
93   * 
94   * <dt>WEB-INF/overlay.xml</dt>
95   * <dd>This {@link XmlConfiguration} formatted file must exist in the WEB-INF directory of an overlay and is 
96   * used to configure a {@link ContextHandler} or {@link WebAppContext}.  The overlay.xml from the template 
97   * overlay can be used to instantiate the ContextHandler instance, so a derived class maybe used.</dd>
98   * 
99   * <dt>WEB-INF/template.xml</dt>
100  * <dd>This {@link XmlConfiguration} formatted file if it exists in a template or node overlay, is applied to a shared instance of {@link TemplateContext}.
101  * Any ID's created in a template are available as ID's in overlay.xml for an instance.</dd>
102  * 
103  * <dt>WEB-INF/webdefault.xml</dt>
104  * <dd>If present in an overlay, then the most specific version is passed to 
105  * {@link WebAppContext#setDefaultsDescriptor(String)}. Typically this is set in the template overlay.</dd>
106  * 
107  * <dt>WEB-INF/web-overlay.xml</dt>
108  * <dd>The web-overlay.xml file of an overlay is applied to a web application as 
109  * with {@link WebAppContext#addOverrideDescriptor(String)}. This allows incremental changes to web.xml without
110  * totally replacing it (see webapp). Typically this is used to set init parameters.</dd>
111  * 
112  * <dt>.</dt>
113  * <dd>This root directory contains static content that overlays the static content of the webapp
114  * or earlier overlays. Using this directory, files like index.html or logo.png can be added or replaced. It can
115  * also be used to replace files within WEB-INF including web.xml classes and libs.</dd>
116  * </dl>
117  * <p>
118  * Any init parameters set on the context, filters or servlets may have parameterized values, with the parameters 
119  * including:
120  * <dl>
121  * <dt>${overlays.dir}</dt>
122  * <dd>the root overlay scan directory as a canonical file name.</dd>
123  * <dt>${overlay.webapp}</dt>
124  * <dd>the webapp name, same as {@link Webapp#getName()}.</dd>
125  * <dt>${overlay.template}</dt>
126  * <dd>the  template name, as {@link Template#getName()}.</dd>
127  * <dt>${overlay.template.name}</dt>
128  * <dd>the  template classifier, as {@link Template#getTemplateName()}.</dd>
129  * <dt>${overlay.template.classifier}</dt>
130  * <dd>the  template classifier, as {@link Template#getClassifier()()}.</dd>
131  * <dt>${overlay.node}</dt>
132  * <dd>the node name, as {@link Node#getName()}.</dd>
133  * <dt>${overlay.instance}</dt>
134  * <dd>the instance name, {@link Instance#getName()}.</dd>
135  * <dt>${overlay.instance.classifier}</dt>
136  * <dd>the instance name, {@link Instance#getClassifier()()}.</dd>
137  * <dt>${*}</dt>
138  * <dd>Any properties obtained via {@link #getConfigurationManager()}.{@link ConfigurationManager#getProperties()}</dd>
139  * <dd></dd>
140  * </dl>
141  * <p>
142  * The OverlayedAppProvider will scan the "webapps", "templates", "nodes" and "instances" subdirectories of 
143  * the directory configured with {@link #setScanDir(File)}. New webapps and overlays and modified files within 
144  * the overlays will trigger hot deployment, redeployment or undeployment.   The scan for modified files is 
145  * restricted to only top level files (eg overlay.xml) and the files matching WEB-INF/*.xml WEB-INF/lib/*
146  * and WEB-INF/classes/*.  The webapps/overlays may be directory structures or war/jar archives.
147  * <p>
148  * The filenames of the templates and instances are used to match them together and with a webapplication.
149  * A webapp may be named anyway, but it is good practise to include a version number (eg webapps/foo-1.2.3.war
150  * or webapps/foo-1.2.3/).   A template for that webapplication must have a name that includes the template name 
151  * and the war name separated by '=' (eg templates/myFoo=foo-1.2.3.jar or  templates/myFoo=foo-1.2.3/).
152  * An instance overlay is named with the template name and an arbitrary instance name separated by '='
153  * (eg instances/myFoo=instance1.jar instances/myFoo=instance2/ etc.).
154  * <p>
155  * If a template name does not include a webapp name, then the template is created as a ContextHandler
156  * instead of a WebAppContext (with the exact type being determined by overlay.xml).
157  */
158 public class OverlayedAppProvider extends AbstractLifeCycle implements AppProvider
159 {
160     private final static Logger __log=org.eclipse.jetty.util.log.Log.getLogger("OverlayedAppProvider");
161     /**
162      * Property set for overlay.xml and template.xml files that gives the root overlay scan directory as a canonical file name.
163      */
164     public final static String OVERLAYS_DIR="overlays.dir";
165     /**
166      *  Property set for overlay.xml and template.xml files that gives the current webapp name, as {@link Webapp#getName()}.
167      */
168     public final static String OVERLAY_WEBAPP="overlay.webapp";
169     /**
170      *  Property set for overlay.xml and template.xml files that gives the current template full name, as {@link Template#getName()}.
171      */
172     public final static String OVERLAY_TEMPLATE="overlay.template";
173     /**
174      *  Property set for overlay.xml and template.xml files that gives the current template name, as {@link Template#getTemplateName()}.
175      */
176     public final static String OVERLAY_TEMPLATE_NAME="overlay.template.name";
177     /**
178      *  Property set for overlay.xml and template.xml files that gives the current template classifier, as {@link Template#getClassifier()}.
179      */
180     public final static String OVERLAY_TEMPLATE_CLASSIFIER="overlay.template.classifier";
181     /**
182      *  Property set for overlay.xml and template.xml files that gives the current node name, as {@link Node#getName()}.
183      */
184     public final static String OVERLAY_NODE="overlay.node";
185     /**
186      *  Property set for overlay.xml and template.xml files that gives the current instance name, {@link Instance#getName()}.
187      */
188     public final static String OVERLAY_INSTANCE="overlay.instance";
189     /**
190      *  Property set for overlay.xml and template.xml files that gives the current instance clasifier, {@link Instance#getClassifier()}.
191      */
192     public final static String OVERLAY_INSTANCE_CLASSIFIER="overlay.instance.classifier";
193     
194     public final static String WEBAPPS="webapps";
195     public final static String TEMPLATES="templates";
196     public final static String NODES="nodes";
197     public final static String INSTANCES="instances";
198 
199     public final static String LIB="WEB-INF/lib-overlay";
200     public final static String WEBAPP=".";
201     public final static String OVERLAY_XML="WEB-INF/overlay.xml";
202     public final static String TEMPLATE_XML="WEB-INF/template.xml";
203     public final static String WEB_DEFAULT_XML="WEB-INF/web-default.xml";
204     public final static String WEB_FRAGMENT_XML="WEB-INF/web-overlay.xml";
205     
206     enum Monitor { WEBAPPS,TEMPLATES,NODES,INSTANCES} ;
207     
208     public final static List<Pattern> __scanPatterns = new ArrayList<Pattern>();
209     
210     static 
211     {
212         List<String> regexes = new ArrayList<String>();
213 
214         for (String s:new String[] {".war",".jar","/WEB-INF/syslib/[^/]*","/WEB-INF/lib/[^/]*","/WEB-INF/classes/[^/]*","/WEB-INF/[^/]*\\.xml",})
215         {
216             regexes.add(WEBAPPS+"/[^/]*"+s);
217             regexes.add(TEMPLATES+"/[^/]*"+s);
218             regexes.add(NODES+"/[^/]*"+s);
219             regexes.add(INSTANCES+"/[^/]*"+s);
220         }
221         
222         for (String s: regexes)
223             __scanPatterns.add(Pattern.compile(s,Pattern.CASE_INSENSITIVE));
224     };
225     
226     private String _nodeName;
227     private File _scanDir;
228     private File _tmpDir;
229     private String _scanDirURI;
230     private long _loading;
231     private Node _node;
232     private final Map<String,Webapp> _webapps = new HashMap<String,Webapp>();
233     private final Map<String,Template> _templates = new HashMap<String,Template>();
234     private final Map<String,Instance> _instances = new HashMap<String,Instance>();
235     private final Map<String,OverlayedApp> _deployed = new HashMap<String,OverlayedApp>();
236     private final Map<String,TemplateContext> _shared = new HashMap<String, TemplateContext>();
237     private boolean _copydir=false;
238     private DeploymentManager _deploymentManager;
239     private ConfigurationManager _configurationManager;
240     private String _serverID="Server";
241     private final Set<Layer> _removedLayers = new HashSet<Layer>();
242     private Timer _sessionScavenger = new Timer();
243     
244     private final Scanner _scanner = new Scanner();
245     private final Scanner.BulkListener _listener = new Scanner.BulkListener()
246     {  
247         public void filesChanged(List<String> filenames) throws Exception
248         {
249             __log.debug("Changed {}",filenames);
250             
251             Set<String> changes = new HashSet<String>();
252             for (String filename:filenames)
253             {
254                 
255                 File file=new File(filename);
256                 if (file.getName().startsWith(".") || file.getName().endsWith(".swp"))
257                     continue;
258                 
259                 String relname=file.toURI().getPath().substring(_scanDirURI.length());
260                                 
261                 File rel = new File(relname);
262                 
263                 String dir=null;
264                 String name=null;
265                 String parent=rel.getParent();
266                 while (parent!=null)
267                 {
268                     name=rel.getName();
269                     dir=parent;
270                     rel=rel.getParentFile();
271                     parent=rel.getParent();
272                 }
273                 
274                 String uri=dir+"/"+name;
275 
276                 for (Pattern p : __scanPatterns)
277                 {
278                     if (p.matcher(relname).matches())
279                     {
280                         __log.debug("{} == {}",relname,p.pattern());
281                         changes.add(uri);
282                     }
283                     else
284                         __log.debug("{} != {}",relname,p.pattern());
285                 }
286             }
287             
288             if (changes.size()>0)
289                 OverlayedAppProvider.this.updateLayers(changes);
290         }
291     };
292     
293 
294     /* ------------------------------------------------------------ */
295     public OverlayedAppProvider()
296     {
297         try
298         {
299             _nodeName=InetAddress.getLocalHost().getHostName();
300         }
301         catch(UnknownHostException e)
302         {
303             __log.debug(e);
304             _nodeName="unknown";
305         }
306     }
307 
308 
309 
310     /* ------------------------------------------------------------ */
311     public void setDeploymentManager(DeploymentManager deploymentManager)
312     {
313         _deploymentManager=deploymentManager;
314     }
315 
316     /* ------------------------------------------------------------ */
317     public DeploymentManager getDeploymentManager()
318     {
319         return _deploymentManager;
320     }
321 
322     /* ------------------------------------------------------------ */
323     public ConfigurationManager getConfigurationManager()
324     {
325         return _configurationManager;
326     }
327     
328     /* ------------------------------------------------------------ */
329     /** Set the configurationManager.
330      * @param configurationManager the configurationManager to set
331      */
332     public void setConfigurationManager(ConfigurationManager configurationManager)
333     {
334         _configurationManager = configurationManager;
335     }
336 
337     /* ------------------------------------------------------------ */
338     /**
339      * @return The name in {@link XmlConfiguration#getIdMap()} of the {@link Server} instance. Default "Server".
340      */
341     public String getServerID()
342     {
343         return _serverID;
344     }
345 
346     /* ------------------------------------------------------------ */
347     /**
348      * @param serverID The name in {@link XmlConfiguration#getIdMap()} of the {@link Server} instance.
349      */
350     public void setServerID(String serverID)
351     {
352         _serverID = serverID;
353     }
354     
355     
356     /**
357      * Create Context Handler.
358      * <p>
359      * Callback from the deployment manager to create a context handler instance.
360      * @see org.eclipse.jetty.deploy.AppProvider#createContextHandler(org.eclipse.jetty.deploy.App)
361      */
362     public synchronized ContextHandler createContextHandler(App app) throws Exception
363     {
364         final OverlayedApp overlayed = (OverlayedApp)app;
365         final String origin = overlayed.getOriginId();
366         final Instance instance = overlayed.getInstance();
367         final Template template = instance.getTemplate();
368         final Webapp webapp = template.getWebapp();
369         final Node node = _node;
370         
371         // remember the original loader
372         ClassLoader orig_loader = Thread.currentThread().getContextClassLoader();
373         try
374         {
375             // Look for existing shared resources
376             String key=(node==null?"":node.getLoadedKey())+template.getLoadedKey()+(webapp==null?"":webapp.getLoadedKey());
377             instance.setSharedKey(key);
378            
379             TemplateContext shared=_shared.get(key);
380             // Create shared resourced
381             if (shared==null)
382                 shared=createTemplateContext(key,webapp,template,node,orig_loader);
383             
384             // Build the instance lib loader
385             ClassLoader shared_loader = shared.getWebappLoader()!=null?shared.getWebappLoader():(shared.getLibLoader()!=null?shared.getLibLoader():orig_loader);
386             ClassLoader loader = shared_loader;
387             Resource instance_lib = instance.getResource(LIB);
388             if (instance_lib.exists())
389             {
390                 List<URL> libs = new ArrayList<URL>();
391                 for (String jar :instance_lib.list())
392                 {
393                     if (!jar.toLowerCase(Locale.ENGLISH).endsWith(".jar"))
394                         continue;
395                     libs.add(instance_lib.addPath(jar).getURL());
396                 }
397                 
398                 __log.debug("{}: libs={}",origin,libs);
399                 loader = URLClassLoader.newInstance(libs.toArray(new URL[]{}),loader);
400             }
401             
402             // set the thread loader
403             Thread.currentThread().setContextClassLoader(loader);
404 
405             // Create properties to be shared by overlay.xmls
406             Map<String,Object> idMap = new HashMap<String,Object>();
407             idMap.putAll(shared.getIdMap());
408             idMap.put(_serverID,getDeploymentManager().getServer());
409             
410             // Create the instance context for the template
411             ContextHandler context=null;
412                 
413             Resource template_context_xml = template.getResource(OVERLAY_XML);
414             if (template_context_xml.exists())
415             {
416                 __log.debug("{}: overlay.xml={}",origin,template_context_xml);
417                 XmlConfiguration xmlc = newXmlConfiguration(template_context_xml.getURL(),idMap,template,instance);
418                 context=(ContextHandler)xmlc.configure();
419                 idMap=xmlc.getIdMap();
420             }
421             else if (webapp==null)
422                 // If there is no webapp, this is a plain context
423                 context=new ContextHandler();
424             else
425                 // It is a webapp context
426                 context=new WebAppContext();
427 
428             // Set the resource base
429             final Resource instance_webapp = instance.getResource(WEBAPP);
430             if (instance_webapp.exists())
431             {   
432                 context.setBaseResource(new ResourceCollection(instance_webapp,shared.getBaseResource()));
433 
434                 // Create the resource cache
435                 ResourceCache cache = new ResourceCache(shared.getResourceCache(),instance_webapp,context.getMimeTypes(),false,false);
436                 context.setAttribute(ResourceCache.class.getCanonicalName(),cache);
437             }
438             else
439             {
440                 context.setBaseResource(shared.getBaseResource());
441                 context.setAttribute(ResourceCache.class.getCanonicalName(),shared.getResourceCache());
442             }
443             __log.debug("{}: baseResource={}",origin,context.getResourceBase());
444             
445             // Set the shared session scavenger timer
446             context.setAttribute("org.eclipse.jetty.server.session.timer", _sessionScavenger);
447             
448             // Apply any node or instance overlay.xml
449             for (Resource context_xml : getLayeredResources(OVERLAY_XML,node,instance))
450             {
451                 __log.debug("{}: overlay.xml={}",origin,context_xml);
452                 XmlConfiguration xmlc = newXmlConfiguration(context_xml.getURL(),idMap,template,instance);
453                 xmlc.getIdMap().put("Cache",context.getAttribute(ResourceCache.class.getCanonicalName()));
454                 xmlc.configure(context);
455                 idMap=xmlc.getIdMap();
456             }
457 
458             // Is it a webapp?
459             if (context instanceof WebAppContext)
460             {
461                 final WebAppContext webappcontext = (WebAppContext)context;
462                 
463                 if (Arrays.asList(((WebAppContext)context).getServerClasses()).toString().equals(Arrays.asList(WebAppContext.__dftServerClasses).toString()))
464                 {
465                     __log.debug("clear server classes");
466                     webappcontext.setServerClasses(null);
467                 }
468                     
469                 // set classloader
470                 webappcontext.setCopyWebDir(false);
471                 webappcontext.setCopyWebInf(false);
472                 webappcontext.setExtractWAR(false);
473                 
474                 if (instance_webapp.exists())
475                 {
476                     final Resource classes=instance_webapp.addPath("WEB-INF/classes");
477                     final Resource lib=instance_webapp.addPath("WEB-INF/lib");
478                 
479                     if (classes.exists()||lib.exists())
480                     {
481                         final AtomicBoolean locked =new AtomicBoolean(false);
482                         
483                         WebAppClassLoader webapp_loader=new WebAppClassLoader(loader,webappcontext)
484                         {
485                             @Override
486                             public void addClassPath(Resource resource) throws IOException
487                             {
488                                 if (!locked.get())
489                                     super.addClassPath(resource);
490                             }
491 
492                             @Override
493                             public void addClassPath(String classPath) throws IOException
494                             {
495                                 if (!locked.get())
496                                     super.addClassPath(classPath);
497                             }
498 
499                             @Override
500                             public void addJars(Resource lib)
501                             {
502                                 if (!locked.get())
503                                     super.addJars(lib);
504                             }
505                         };
506 
507                         if (classes.exists())
508                             webapp_loader.addClassPath(classes);
509                         if (lib.exists())
510                             webapp_loader.addJars(lib);
511                         locked.set(true);
512                         
513                         loader=webapp_loader;
514                     }
515                 }
516                 
517                 // Make sure loader is unique for JNDI
518                 if (loader==shared_loader)
519                     loader = new URLClassLoader(new URL[]{},shared_loader);
520 
521                 // add default descriptor
522                 List<Resource> webdefaults=getLayeredResources(WEB_DEFAULT_XML,instance,node,template);
523                 if (webdefaults.size()>0)
524                 {
525                     Resource webdefault = webdefaults.get(0);
526                     __log.debug("{}: defaultweb={}",origin,webdefault);
527                     webappcontext.setDefaultsDescriptor(webdefault.toString());
528                 }
529                 
530                 // add overlay descriptors
531                 for (Resource override : getLayeredResources(WEB_FRAGMENT_XML,template,node,instance))
532                 {
533                     __log.debug("{}: web override={}",origin,override);
534                     webappcontext.addOverrideDescriptor(override.toString());
535                 }
536             }
537 
538             context.setClassLoader(loader);
539 
540             __log.debug("{}: baseResource={}",origin,context.getBaseResource());
541             
542             Resource jetty_web_xml = context.getResource("/WEB-INF/"+JettyWebXmlConfiguration.JETTY_WEB_XML);
543             if (jetty_web_xml!=null && jetty_web_xml.exists())
544                 context.setAttribute(JettyWebXmlConfiguration.XML_CONFIGURATION,newXmlConfiguration(jetty_web_xml.getURL(),idMap,template,instance));
545             
546             // Add listener to expand parameters from descriptors before other listeners execute
547             Map<String,String> params = new HashMap<String,String>();
548             populateParameters(params,template,instance);
549             context.addEventListener(new ParameterExpander(params,context));
550             
551             System.err.println("created:\n"+context.dump());
552             
553             return context;
554         }
555         finally
556         {
557             Thread.currentThread().setContextClassLoader(orig_loader);
558         }
559     }
560 
561     /* ------------------------------------------------------------ */
562     private XmlConfiguration newXmlConfiguration(URL url, Map<String, Object> idMap, Template template, Instance instance) throws SAXException, IOException
563     {
564         XmlConfiguration xmlc = new XmlConfiguration(url);
565         populateParameters(xmlc.getProperties(),template,instance);
566         xmlc.getIdMap().putAll(idMap);
567         
568         return xmlc;
569     }
570 
571     /* ------------------------------------------------------------ */
572     private void populateParameters(Map<String,String> params,Template template, Instance instance)
573     {
574         try
575         {
576             params.put(OVERLAYS_DIR,_scanDir.getCanonicalPath());
577             if (template!=null)
578             {
579                 params.put(OVERLAY_TEMPLATE,template.getName());
580                 params.put(OVERLAY_TEMPLATE_NAME,template.getTemplateName());
581                 params.put(OVERLAY_TEMPLATE_CLASSIFIER,template.getClassifier());
582                 params.put(OVERLAY_WEBAPP,template.getWebapp()==null?null:template.getWebapp().getName());
583             }
584             if (_node!=null)
585                 params.put(OVERLAY_NODE,_node.getName());
586             if (instance!=null)
587             {
588                 params.put(OVERLAY_INSTANCE,instance.getName());
589                 params.put(OVERLAY_INSTANCE_CLASSIFIER,instance.getClassifier());
590             }
591             if (getConfigurationManager()!=null)
592                 params.putAll(getConfigurationManager().getProperties());
593         }
594         catch(Exception e)
595         {
596             throw new RuntimeException(e);
597         }
598     }
599 
600 
601     /* ------------------------------------------------------------ */
602     private TemplateContext createTemplateContext(final String key, Webapp webapp, Template template, Node node, ClassLoader parent) throws Exception
603     {
604         __log.info("created {}",key);
605         
606         // look for libs
607         // If we have libs directories, create classloader and make it available to
608         // the XMLconfiguration
609         List<URL> libs = new ArrayList<URL>();
610         for (Resource lib : getLayeredResources(LIB,node,template))
611         {
612             for (String jar :lib.list())
613             {
614                 if (!jar.toLowerCase(Locale.ENGLISH).endsWith(".jar"))
615                     continue;
616                 libs.add(lib.addPath(jar).getURL());
617             }
618         }
619         final ClassLoader libLoader;
620         if (libs.size()>0)
621         {
622             __log.debug("{}: libs={}",key,libs);
623             libLoader=new URLClassLoader(libs.toArray(new URL[]{}),parent)
624             {
625                 public String toString() {return "libLoader@"+Long.toHexString(hashCode())+"-lib-"+key;}
626             };
627             
628         }
629         else
630             libLoader=parent;
631         
632         Thread.currentThread().setContextClassLoader(libLoader);
633         
634         
635         // Make the shared resourceBase
636         List<Resource> bases = new ArrayList<Resource>();
637         for (Resource wa : getLayers(node,template))
638             bases.add(wa);
639         if (webapp!=null)
640             bases.add(webapp.getBaseResource());
641         Resource baseResource = bases.size()==1?bases.get(0):new ResourceCollection(bases.toArray(new Resource[bases.size()]));
642         __log.debug("{}: baseResource={}",key,baseResource);
643         
644         
645         // Make the shared context
646         TemplateContext shared = new TemplateContext(key,getDeploymentManager().getServer(),baseResource,libLoader);
647         _shared.put(key,shared);
648 
649         
650         // Create properties to be shared by overlay.xmls
651         Map<String,Object> idMap = new HashMap<String,Object>();
652         idMap.put(_serverID,getDeploymentManager().getServer());
653 
654         
655         // Create the shared context for the template
656         // This instance will never be start, but is used to capture the 
657         // shared results of running the template and node overlay.xml files.
658         // If there is a template overlay.xml, give it the chance to create the ContextHandler instance
659         // otherwise create an instance ourselves
660         for (Resource template_xml : getLayeredResources(TEMPLATE_XML,template,node))
661         {
662             __log.debug("{}: template.xml={}",key,template_xml);
663             XmlConfiguration xmlc = newXmlConfiguration(template_xml.getURL(),idMap,template,null);
664             xmlc.getIdMap().putAll(idMap);
665             xmlc.configure(shared);
666             idMap=xmlc.getIdMap();
667         }
668         
669         shared.setIdMap(idMap);
670         shared.start();
671         
672         return shared;
673     }
674 
675     /* ------------------------------------------------------------ */
676     /**
677      * @return The node name (defaults to hostname)
678      */
679     public String getNodeName()
680     {
681         return _nodeName;
682     }
683 
684     /* ------------------------------------------------------------ */
685     /**
686      * @param nodeName Set the node name
687      */
688     public void setNodeName(String nodeName)
689     {
690         _nodeName = nodeName;
691     }
692 
693     /* ------------------------------------------------------------ */
694     /** Get the scanDir.
695      * @return the scanDir
696      */
697     public File getScanDir()
698     {
699         return _scanDir;
700     }
701 
702     /* ------------------------------------------------------------ */
703     /** Set the scanDir.
704      * @param scanDir the scanDir to set
705      */
706     public void setScanDir(File scanDir)
707     {
708         _scanDir = scanDir;
709     }
710 
711     /* ------------------------------------------------------------ */
712     /** Set the temporary directory.
713      * @param tmpDir the directory for temporary files.  If null, then getScanDir()+"/tmp" is used if it exists, else the system default is used.
714      */
715     public void setTmpDir(File tmpDir)
716     {
717         _tmpDir=tmpDir;
718     }
719 
720     /* ------------------------------------------------------------ */
721     /** Get the temporary directory.
722      * return the tmpDir.  If null, then getScanDir()+"/tmp" is used if it exists, else the system default is used.
723      */
724     public File getTmpDir()
725     {
726         return _tmpDir;
727     }
728     
729     /* ------------------------------------------------------------ */
730     /**
731      * @return The scan interval
732      * @see org.eclipse.jetty.util.Scanner#getScanInterval()
733      */
734     public int getScanInterval()
735     {
736         return _scanner.getScanInterval();
737     }
738 
739     /* ------------------------------------------------------------ */
740     /**
741      * @param scanInterval The scan interval
742      * @see org.eclipse.jetty.util.Scanner#setScanInterval(int)
743      */
744     public void setScanInterval(int scanInterval)
745     {
746         _scanner.setScanInterval(scanInterval);
747     }
748 
749     /* ------------------------------------------------------------ */
750     /**
751      * @see org.eclipse.jetty.util.Scanner#scan()
752      */
753     public void scan()
754     {
755         _scanner.scan();
756     }
757 
758     /* ------------------------------------------------------------ */
759     /**
760      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
761      */
762     @Override
763     protected void doStart() throws Exception
764     {
765         __log.info("Node={} Scan=",_nodeName,_scanDir);
766         if (_scanDir==null || !_scanDir.exists())
767             throw new IllegalStateException("!scandir");
768 
769         _scanDirURI=_scanDir.toURI().getPath();
770         _scanner.setScanDepth(6); // enough for templates/name/webapps/WEB-INF/lib/foo.jar
771         List<File> dirs = Arrays.asList(new File[]
772                                                  {
773                 new File(_scanDir,WEBAPPS),
774                 new File(_scanDir,TEMPLATES),
775                 new File(_scanDir,NODES),
776                 new File(_scanDir,INSTANCES)
777             });
778         for (File file : dirs)
779         {
780             if (!file.exists() && !file.isDirectory())
781                 __log.warn("No directory: "+file.getAbsolutePath());
782         }
783         _scanner.setScanDirs(dirs);
784         _scanner.addListener(_listener);
785         _scanner.start();
786         
787         super.doStart();
788     }
789 
790     /* ------------------------------------------------------------ */
791     /**
792      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
793      */
794     @Override
795     protected void doStop() throws Exception
796     {
797         _scanner.removeListener(_listener);
798         _scanner.stop();
799         
800         if (_deploymentManager.isRunning())
801         {
802             for (App app: _deployed.values())
803             _deploymentManager.removeApp(app);
804         }
805         _deployed.clear();
806         
807         for (Layer layer : _webapps.values())
808             layer.release();
809         _webapps.clear();
810         for (Layer layer : _templates.values())
811             layer.release();
812         _templates.clear();
813         if (_node!=null)
814             _node.release();
815         for (Layer layer : _instances.values())
816             layer.release();
817         _instances.clear();
818         
819         super.doStop();
820     }
821     
822     /* ------------------------------------------------------------ */
823     protected synchronized void updateLayers(Set<String> layerURIs)
824     {
825         _loading=System.currentTimeMillis();
826         for (String ruri: layerURIs)
827         {
828             try
829             {
830                 // Decompose the name
831                 File directory;
832                 File archive;
833                 File origin = new File(new URI(_scanDir.toURI()+ruri));
834                 String name=origin.getName();
835                 
836                 Monitor monitor = Monitor.valueOf(origin.getParentFile().getName().toUpperCase(Locale.ENGLISH));
837                 
838                 String ext=".war";
839                 
840                 // check directory vs archive 
841                 if (origin.isDirectory() || !origin.exists() && !ruri.toLowerCase(Locale.ENGLISH).endsWith(ext))
842                 {
843                     // directories have priority over archives
844                     directory=origin;
845                     archive=new File(directory.toString()+ext);
846                 }
847                 else
848                 {
849                     // check extension name
850                     if (!ruri.toLowerCase(Locale.ENGLISH).endsWith(ext))
851                         continue;
852 
853                     name=name.substring(0,name.length()-4);
854                     archive=origin;
855                     directory=new File(new URI(_scanDir.toURI()+ruri.substring(0,ruri.length()-4)));
856                     
857                     // Look to see if directory exists
858                     if (directory.exists())
859                     {
860                         __log.info("Directory exists, ignoring change to {}",ruri);
861                         continue;
862                     }
863                 }
864                 
865                 Layer layer=null;
866                 
867                 switch(monitor)
868                 {
869                     case WEBAPPS:
870                         if (origin.exists())
871                             layer=loadWebapp(name,origin);
872                         else
873                         {
874                             removeWebapp(name);
875                             if (origin==directory && archive.exists())
876                                 layer=loadWebapp(name,archive);
877                         }
878                         
879                         break;
880                         
881                     case TEMPLATES:
882                         if (origin.exists())
883                             layer=loadTemplate(name,origin);
884                         else 
885                         {
886                             removeTemplate(name);
887                             if (origin==directory && archive.exists())
888                                 layer=loadTemplate(name,archive);
889                         }
890                         break;
891                         
892                     case NODES:
893                         if (name.equalsIgnoreCase(_nodeName))
894                         {
895                             if (origin.exists())
896                                 layer=loadNode(origin);
897                             else
898                             {
899                                 removeNode();
900                                 if (origin==directory && archive.exists())
901                                     layer=loadNode(archive);
902                             }
903                         }
904                         break;
905                         
906                     case INSTANCES:
907                         if (origin.exists())
908                             layer=loadInstance(name,origin);
909                         else
910                         {
911                             removeInstance(name);
912                             if (origin==directory && archive.exists())
913                                 layer=loadInstance(name,archive);
914                         }
915                         break;
916                         
917                 }
918                 
919                 if (layer!=null)
920                     __log.info("loaded {}",layer.getLoadedKey());
921             }
922             catch(Exception e)
923             {
924                 __log.warn(e);
925             }
926         }
927         
928         redeploy();
929 
930         // Release removed layers
931         for (Layer layer : _removedLayers)
932         {    
933             if (layer!=null)
934             {
935                 __log.info("unload {}",layer.getLoadedKey());
936                 layer.release();
937             }
938         }
939         _removedLayers.clear();
940         
941         if (__log.isDebugEnabled())
942         {
943             System.err.println("updated:");
944             System.err.println("java:"+javaRootURLContext.getRoot().dump());
945             System.err.println("local:"+localContextRoot.getRoot().dump());
946             if (getDeploymentManager()!=null && getDeploymentManager().getServer()!=null)
947                 System.err.println(getDeploymentManager().getServer().dump());
948         }
949     }
950 
951     /* ------------------------------------------------------------ */
952     protected File tmpdir(String name,String suffix) throws IOException
953     {
954         File dir=_tmpDir;
955         if (dir==null || !dir.isDirectory() || !dir.canWrite())
956         {
957             dir=new File(_scanDir,"tmp");
958             if (!dir.isDirectory() || !dir.canWrite())
959                 dir=null;
960         }
961         
962         File tmp = File.createTempFile(name+"_","."+suffix,dir);
963         tmp=tmp.getCanonicalFile();
964         if (tmp.exists())
965             IO.delete(tmp);
966         tmp.mkdir();
967         tmp.deleteOnExit();
968         return tmp;
969     }
970 
971     /* ------------------------------------------------------------ */
972     /**
973      * Walks the defined webapps, templates, nodes and instances to 
974      * determine what should be deployed, then adjust reality to match.
975      */
976     protected void redeploy()
977     {
978         Map<String,Template> templates = new ConcurrentHashMap<String,Template>();
979         
980         // Check for duplicate templates
981         for (Template template : _templates.values())
982         {
983             Template other=templates.get(template.getTemplateName());
984             if (other!=null)
985             {
986                 __log.warn("Multiple Templates: {} & {}",template.getName(),other.getName());
987                 if (other.getName().compareToIgnoreCase(template.getName())<=0)
988                     continue;
989             }
990             templates.put(template.getTemplateName(),template);
991         }
992         
993         // Match webapps to templates
994         for (Template template : templates.values())
995         {
996             String webappname=template.getClassifier();
997             
998             if (webappname==null)
999                 continue;
1000             
1001             Webapp webapp = _webapps.get(webappname);
1002             
1003             if (webapp==null)
1004             {
1005                 __log.warn("No webapp found for template: {}",template.getName());
1006                 templates.remove(template.getTemplateName());
1007             }
1008             else
1009             {
1010                 template.setWebapp(webapp);
1011             }
1012         }
1013 
1014         // Match instance to templates and check if what needs to be deployed or undeployed.
1015         Set<String> deployed = new HashSet<String>();
1016         List<Instance> deploy = new ArrayList<Instance>();
1017        
1018         for (Instance instance : _instances.values())
1019         {
1020             Template template=templates.get(instance.getTemplateName());
1021             instance.setTemplate(template);
1022             if (template!=null)
1023             {
1024                 String key=instance.getInstanceKey();
1025                 App app = _deployed.get(key);
1026                 if (app==null)
1027                     deploy.add(instance);
1028                 else
1029                     deployed.add(key);
1030             }
1031         }
1032         
1033         // Look for deployed apps that need to be undeployed
1034         List<String> undeploy = new ArrayList<String>();
1035         for (String key : _deployed.keySet())
1036         {
1037             if (!deployed.contains(key))
1038                 undeploy.add(key);
1039         }
1040         
1041         // Do the undeploys
1042         for (String key : undeploy)
1043         {
1044             App app = _deployed.remove(key);
1045             if (app!=null)
1046             {
1047                 __log.info("Undeploy {}",key);
1048                 _deploymentManager.removeApp(app);
1049             }
1050         }
1051         
1052         // ready the deploys
1053         for (Instance instance : deploy)
1054         {
1055             String key=instance.getInstanceKey();
1056             OverlayedApp app = new OverlayedApp(_deploymentManager,this,key,instance);
1057             _deployed.put(key,app);
1058         }
1059 
1060         // Remove unused Shared stuff
1061         Set<String> sharedKeys = new HashSet<String>(_shared.keySet());
1062         for (OverlayedApp app : _deployed.values())
1063         {
1064             Instance instance = app.getInstance();
1065             sharedKeys.remove(instance.getSharedKey());
1066         }
1067         for (String sharedKey: sharedKeys)
1068         {
1069             __log.debug("Remove "+sharedKey);
1070             TemplateContext shared=_shared.remove(sharedKey);
1071             if (shared!=null)
1072             {
1073                 try
1074                 {
1075                     shared.stop();
1076                 }
1077                 catch(Exception e)
1078                 {
1079                     __log.warn(e);
1080                 }
1081                 shared.destroy();
1082             }
1083         }
1084 
1085         // Do the deploys
1086         for (Instance instance : deploy)
1087         {
1088             String key=instance.getInstanceKey();
1089             OverlayedApp app = _deployed.get(key);
1090             __log.info("Deploy {}",key);
1091             _deploymentManager.addApp(app);
1092         }
1093 
1094 
1095     }
1096 
1097     /* ------------------------------------------------------------ */
1098     protected void removeInstance(String name)
1099     {
1100         _removedLayers.add(_instances.remove(name));
1101     }
1102 
1103     /* ------------------------------------------------------------ */
1104     protected Instance loadInstance(String name, File origin)
1105         throws IOException
1106     {
1107         Instance instance=new Instance(name,origin);
1108         _removedLayers.add(_instances.put(name,instance));
1109         return instance;
1110     }
1111 
1112     /* ------------------------------------------------------------ */
1113     protected void removeNode()
1114     {
1115         if (_node!=null)
1116             _removedLayers.add(_node);
1117         _node=null;
1118     }
1119 
1120     /* ------------------------------------------------------------ */
1121     protected Node loadNode(File origin)
1122         throws IOException
1123     {
1124         if (_node!=null)
1125             _removedLayers.add(_node);
1126         _node=new Node(_nodeName,origin);
1127         return _node;
1128     }
1129 
1130     /* ------------------------------------------------------------ */
1131     protected void removeTemplate(String name)
1132     {
1133         _removedLayers.add(_templates.remove(name));
1134     }
1135 
1136     /* ------------------------------------------------------------ */
1137     protected Template loadTemplate(String name, File origin)
1138         throws IOException
1139     {
1140         Template template=new Template(name,origin);
1141         _removedLayers.add(_templates.put(name,template));
1142         return template;
1143     }
1144 
1145     protected void removeWebapp(String name)
1146     {
1147         _removedLayers.add(_webapps.remove(name));
1148     }
1149 
1150     /* ------------------------------------------------------------ */
1151     protected Webapp loadWebapp(String name, File origin)
1152         throws IOException
1153     {
1154         Webapp webapp = new Webapp(name,origin);
1155         _removedLayers.add(_webapps.put(name,webapp));
1156         return webapp;
1157     }
1158 
1159     /* ------------------------------------------------------------ */
1160     private static List<Resource> getLayers(Layer... layers)
1161     {
1162         List<Resource> resources = new ArrayList<Resource>();
1163         for (Layer layer: layers)
1164         {
1165             if (layer==null)
1166                 continue;
1167             Resource resource = layer.getBaseResource();
1168             if (resource.exists())
1169                 resources.add(resource);
1170         }
1171         return resources;
1172     }
1173     
1174     /* ------------------------------------------------------------ */
1175     private static List<Resource> getLayeredResources(String path, Layer... layers)
1176     {
1177         List<Resource> resources = new ArrayList<Resource>();
1178         for (Layer layer: layers)
1179         {
1180             if (layer==null)
1181                 continue;
1182             Resource resource = layer.getResource(path);
1183             if (resource.exists())
1184                 resources.add(resource);
1185         }
1186         return resources;
1187     }
1188 
1189     /* ------------------------------------------------------------ */
1190     /* ------------------------------------------------------------ */
1191     /* ------------------------------------------------------------ */
1192     class Layer 
1193     {
1194         private final String _name;
1195         private final File _origin;
1196         private final long _loaded=_loading;
1197         private final Resource _resourceBase;
1198         private final boolean _resourceBaseIsCopy;
1199         
1200         public Layer(String name, File origin)
1201             throws IOException
1202         {
1203             super();
1204             _name = name;
1205             _origin = origin;
1206             
1207             Resource resource = Resource.newResource(origin.toURI());
1208             
1209             if (resource.isDirectory())
1210             {
1211                 if (_copydir)
1212                 {
1213                     File tmp=tmpdir(name,"extract");
1214                     __log.info("Extract {} to {}",origin,tmp);
1215                     IO.copyDir(origin,tmp);
1216                     _resourceBase=Resource.newResource(tmp.toURI());
1217                     _resourceBaseIsCopy=true;
1218                 }
1219                 else
1220                 {
1221                     _resourceBase=resource;
1222                     _resourceBaseIsCopy=false;
1223                 }
1224             }
1225             else 
1226             {
1227                 Resource jar = JarResource.newJarResource(resource);
1228                 File tmp=tmpdir(name,"extract");
1229                 __log.info("Extract {} to {}",jar,tmp);
1230                 jar.copyTo(tmp);
1231                 _resourceBase=Resource.newResource(tmp.toURI());
1232                 _resourceBaseIsCopy=true;
1233             }    
1234         }
1235         
1236         public String getName()
1237         {
1238             return _name;
1239         }
1240         
1241         public File getOrigin()
1242         {
1243             return _origin;
1244         }
1245         
1246         public long getLoaded()
1247         {
1248             return _loaded;
1249         }
1250 
1251         public Resource getBaseResource()
1252         {
1253             return _resourceBase;
1254         } 
1255 
1256         public Resource getResource(String path)
1257         {
1258             try
1259             {
1260                 return getBaseResource().addPath(path);
1261             }
1262             catch(Exception e)
1263             {
1264                 __log.warn(e);
1265             }
1266             return null;
1267         }
1268         
1269         public String getLoadedKey()
1270         {
1271             return _name+"@"+_loaded;
1272         }
1273         
1274         public void release()
1275         {
1276             if (_resourceBaseIsCopy)
1277             {
1278                 try
1279                 {
1280                     File file = _resourceBase.getFile();
1281                     if (file!=null)
1282                         IO.delete(file);
1283                 }
1284                 catch(Exception e)
1285                 {
1286                     __log.warn(e);
1287                 }
1288             }
1289         }
1290         
1291         public String toString()
1292         {
1293             return getLoadedKey();
1294         }
1295     }
1296 
1297     class Webapp extends Layer
1298     {
1299         public Webapp(String name, File origin) throws IOException
1300         {
1301             super(name,origin);
1302         }
1303     }
1304     
1305     class Overlay extends Layer
1306     {
1307         public Overlay(String name, File origin) throws IOException
1308         {
1309             super(name,origin);
1310         }
1311     
1312         public Resource getContext()
1313         {
1314             return getResource(OVERLAY_XML);
1315         }
1316     }
1317 
1318     /* ------------------------------------------------------------ */
1319     /* ------------------------------------------------------------ */
1320     /* ------------------------------------------------------------ */
1321     class Node extends Overlay
1322     {
1323         public Node(String name, File origin) throws IOException
1324         {
1325             super(name,origin);
1326         }
1327     }
1328     
1329 
1330     /* ------------------------------------------------------------ */
1331     /* ------------------------------------------------------------ */
1332     /* ------------------------------------------------------------ */
1333     class ClassifiedOverlay extends Overlay
1334     {
1335         private final String _templateName;
1336         private final String _classifier;
1337         
1338         public ClassifiedOverlay(String name, File origin) throws IOException
1339         {
1340             super(name,origin);
1341             
1342             int l=1;
1343             int e=name.indexOf('=');
1344             if (e<0)
1345             {
1346                 l=2;
1347                 e=name.indexOf("--");
1348             }
1349             _templateName=e>=0?name.substring(0,e):name;
1350             _classifier=e>=0?name.substring(e+l):null;
1351         }
1352 
1353         public String getTemplateName()
1354         {
1355             return _templateName;
1356         }
1357 
1358         public String getClassifier()
1359         {
1360             return _classifier;
1361         }
1362     }
1363 
1364     /* ------------------------------------------------------------ */
1365     /* ------------------------------------------------------------ */
1366     /* ------------------------------------------------------------ */
1367     class Template extends ClassifiedOverlay
1368     {
1369         private Webapp _webapp;
1370         
1371         public Webapp getWebapp()
1372         {
1373             return _webapp;
1374         }
1375 
1376         public void setWebapp(Webapp webapp)
1377         {
1378             _webapp = webapp;
1379         }
1380 
1381         public Template(String name, File origin) throws IOException
1382         {
1383             super(name,origin);
1384         }
1385     }
1386 
1387     /* ------------------------------------------------------------ */
1388     /* ------------------------------------------------------------ */
1389     /* ------------------------------------------------------------ */
1390     class Instance extends ClassifiedOverlay
1391     {
1392         Template _template;
1393         String _sharedKey;
1394         
1395         public Instance(String name, File origin) throws IOException
1396         {
1397             super(name,origin);
1398             if (getClassifier()==null)
1399                 throw new IllegalArgumentException("Instance without '=':"+name);
1400         }
1401 
1402         public void setSharedKey(String key)
1403         {
1404             _sharedKey=key;
1405         }
1406 
1407         public String getSharedKey()
1408         {
1409             return _sharedKey;
1410         }
1411 
1412         public void setTemplate(Template template)
1413         {
1414             _template=template;
1415         }
1416 
1417         public Template getTemplate()
1418         {
1419             return _template;
1420         }
1421         
1422         public String getInstanceKey()
1423         {
1424             return 
1425             (_template.getWebapp()==null?"":_template.getWebapp().getLoadedKey())+"|"+
1426             _template.getLoadedKey()+"|"+
1427             (_node==null?"":_node.getLoadedKey())+"|"+
1428             getLoadedKey();
1429         }
1430     }
1431 
1432     /* ------------------------------------------------------------ */
1433     /* ------------------------------------------------------------ */
1434     /* ------------------------------------------------------------ */
1435     static class OverlayedApp extends App
1436     {
1437         final Instance _instance;
1438         
1439         public OverlayedApp(DeploymentManager manager, AppProvider provider, String originId, Instance instance)
1440         {
1441             super(manager,provider,originId);
1442             _instance=instance;
1443         }
1444         
1445         public Instance getInstance()
1446         {
1447             return _instance;
1448         }
1449     }
1450     
1451 
1452     /* ------------------------------------------------------------ */
1453     /* ------------------------------------------------------------ */
1454     /* ------------------------------------------------------------ */
1455     private final class ParameterExpander implements ServletContextListener
1456     {
1457         private final Map<String, String> _params;
1458         private final ContextHandler _ctx;
1459 
1460         private ParameterExpander(Map<String, String> params, ContextHandler ctx)
1461         {
1462             _params = params;
1463             _ctx = ctx;
1464         }
1465 
1466         public void contextInitialized(ServletContextEvent sce)
1467         {
1468             Enumeration<String> e=_ctx.getInitParameterNames();
1469             while (e.hasMoreElements())
1470             {
1471                 String name = e.nextElement();
1472                 _ctx.setInitParameter(name,expandParameter(_ctx.getInitParameter(name)));
1473             }
1474             
1475             ServletHandler servletHandler = _ctx.getChildHandlerByClass(ServletHandler.class);
1476             if (servletHandler!=null)
1477             {
1478                 List<Holder<?>> holders = new ArrayList<Holder<?>>();
1479                 if (servletHandler.getFilters()!=null)
1480                     holders.addAll(Arrays.asList(servletHandler.getFilters()));
1481                 if (servletHandler.getHandler()!=null)
1482                     holders.addAll(Arrays.asList(servletHandler.getServlets()));
1483                 for (Holder<?> holder: holders)
1484                 {
1485                     e=holder.getInitParameterNames();
1486                     while (e.hasMoreElements())
1487                     {
1488                         String name = e.nextElement();
1489                         holder.setInitParameter(name,expandParameter(holder.getInitParameter(name)));
1490                     }
1491                 }
1492             }
1493         }
1494 
1495         private String expandParameter(String value)
1496         {
1497             int i=0;
1498             while (true)
1499             {
1500                 int open=value.indexOf("${",i);
1501                 if (open<0)
1502                     return value;
1503                 int close=value.indexOf("}",open);
1504                 if (close<0)
1505                     return value;
1506                 
1507                 String param = value.substring(open+2,close);
1508                 if (_params.containsKey(param))
1509                 {
1510                     String tmp=value.substring(0,open)+_params.get(param);
1511                     i=tmp.length();
1512                     value=tmp+value.substring(close+1);
1513                 }
1514                 else
1515                     i=close+1;
1516             }
1517         }
1518 
1519         public void contextDestroyed(ServletContextEvent sce)
1520         {
1521         }
1522     }
1523 }