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