View Javadoc

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