| // |
| // ======================================================================== |
| // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.overlays; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.Timer; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.regex.Pattern; |
| |
| import javax.servlet.ServletContextEvent; |
| import javax.servlet.ServletContextListener; |
| |
| import org.eclipse.jetty.deploy.App; |
| import org.eclipse.jetty.deploy.AppProvider; |
| import org.eclipse.jetty.deploy.ConfigurationManager; |
| import org.eclipse.jetty.deploy.DeploymentManager; |
| import org.eclipse.jetty.jndi.java.javaRootURLContext; |
| import org.eclipse.jetty.jndi.local.localContextRoot; |
| import org.eclipse.jetty.server.ResourceCache; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.server.handler.ContextHandler; |
| import org.eclipse.jetty.servlet.Holder; |
| import org.eclipse.jetty.servlet.ServletHandler; |
| import org.eclipse.jetty.util.IO; |
| import org.eclipse.jetty.util.Scanner; |
| import org.eclipse.jetty.util.component.AbstractLifeCycle; |
| import org.eclipse.jetty.util.log.Logger; |
| import org.eclipse.jetty.util.resource.JarResource; |
| import org.eclipse.jetty.util.resource.Resource; |
| import org.eclipse.jetty.util.resource.ResourceCollection; |
| import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; |
| import org.eclipse.jetty.webapp.WebAppClassLoader; |
| import org.eclipse.jetty.webapp.WebAppContext; |
| import org.eclipse.jetty.xml.XmlConfiguration; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Overlayed AppProvider |
| * <p> |
| * This {@link AppProvider} implementation can deploy either {@link WebAppContext}s or plain |
| * {@link ContextHandler}s that are assembled from a series of overlays: |
| * <dl> |
| * <dt>webapp</dt><dd>The webapp overlay is a WAR file or docroot directory. The intent is that |
| * the WAR should be deployed to this AppProvider unchanged from how it was delivered. All configuration |
| * and extension should be able to be done in an overlay.</dd> |
| * <dt>template</dt><dd>A template overlay is applied to a WAR file to configure it for all instances of |
| * the webapp to be deployed in the server(s)</dd> |
| * <dt>node</dt><dd>A node overlay is applied to a template to configure it all instances of the template |
| * with node specific information (eg IP address, DB servers etc.).</dd> |
| * <dt>instance</dt><dd>An instance overlay is applied to a node and/or template to configure it |
| * for a specific instance of the template (eg per tenant configuration).</dd> |
| * </dl> |
| * <p> |
| * Each overlays may provide the following files and subdirectories:<dl> |
| * <dt>WEB-INF/lib-overlay</dt> |
| * <dd>The lib-overlay directory can contain jars that are applied to a {@link URLClassLoader} that is |
| * available before any overlay.xml files are executed, so that classes from these jars may be used by the |
| * overlay.xml.</dd> |
| * |
| * <dt>WEB-INF/overlay.xml</dt> |
| * <dd>This {@link XmlConfiguration} formatted file must exist in the WEB-INF directory of an overlay and is |
| * used to configure a {@link ContextHandler} or {@link WebAppContext}. The overlay.xml from the template |
| * overlay can be used to instantiate the ContextHandler instance, so a derived class maybe used.</dd> |
| * |
| * <dt>WEB-INF/template.xml</dt> |
| * <dd>This {@link XmlConfiguration} formatted file if it exists in a template or node overlay, is applied to a shared instance of {@link TemplateContext}. |
| * Any ID's created in a template are available as ID's in overlay.xml for an instance.</dd> |
| * |
| * <dt>WEB-INF/webdefault.xml</dt> |
| * <dd>If present in an overlay, then the most specific version is passed to |
| * {@link WebAppContext#setDefaultsDescriptor(String)}. Typically this is set in the template overlay.</dd> |
| * |
| * <dt>WEB-INF/web-overlay.xml</dt> |
| * <dd>The web-overlay.xml file of an overlay is applied to a web application as |
| * with {@link WebAppContext#addOverrideDescriptor(String)}. This allows incremental changes to web.xml without |
| * totally replacing it (see webapp). Typically this is used to set init parameters.</dd> |
| * |
| * <dt>.</dt> |
| * <dd>This root directory contains static content that overlays the static content of the webapp |
| * or earlier overlays. Using this directory, files like index.html or logo.png can be added or replaced. It can |
| * also be used to replace files within WEB-INF including web.xml classes and libs.</dd> |
| * </dl> |
| * <p> |
| * Any init parameters set on the context, filters or servlets may have parameterized values, with the parameters |
| * including: |
| * <dl> |
| * <dt>${overlays.dir}</dt> |
| * <dd>the root overlay scan directory as a canonical file name.</dd> |
| * <dt>${overlay.webapp}</dt> |
| * <dd>the webapp name, same as {@link Webapp#getName()}.</dd> |
| * <dt>${overlay.template}</dt> |
| * <dd>the template name, as {@link Template#getName()}.</dd> |
| * <dt>${overlay.template.name}</dt> |
| * <dd>the template classifier, as {@link Template#getTemplateName()}.</dd> |
| * <dt>${overlay.template.classifier}</dt> |
| * <dd>the template classifier, as {@link Template#getClassifier()()}.</dd> |
| * <dt>${overlay.node}</dt> |
| * <dd>the node name, as {@link Node#getName()}.</dd> |
| * <dt>${overlay.instance}</dt> |
| * <dd>the instance name, {@link Instance#getName()}.</dd> |
| * <dt>${overlay.instance.classifier}</dt> |
| * <dd>the instance name, {@link Instance#getClassifier()()}.</dd> |
| * <dt>${*}</dt> |
| * <dd>Any properties obtained via {@link #getConfigurationManager()}.{@link ConfigurationManager#getProperties()}</dd> |
| * <dd></dd> |
| * </dl> |
| * <p> |
| * The OverlayedAppProvider will scan the "webapps", "templates", "nodes" and "instances" subdirectories of |
| * the directory configured with {@link #setScanDir(File)}. New webapps and overlays and modified files within |
| * the overlays will trigger hot deployment, redeployment or undeployment. The scan for modified files is |
| * restricted to only top level files (eg overlay.xml) and the files matching WEB-INF/*.xml WEB-INF/lib/* |
| * and WEB-INF/classes/*. The webapps/overlays may be directory structures or war/jar archives. |
| * <p> |
| * The filenames of the templates and instances are used to match them together and with a webapplication. |
| * A webapp may be named anyway, but it is good practise to include a version number (eg webapps/foo-1.2.3.war |
| * or webapps/foo-1.2.3/). A template for that webapplication must have a name that includes the template name |
| * and the war name separated by '=' (eg templates/myFoo=foo-1.2.3.jar or templates/myFoo=foo-1.2.3/). |
| * An instance overlay is named with the template name and an arbitrary instance name separated by '=' |
| * (eg instances/myFoo=instance1.jar instances/myFoo=instance2/ etc.). |
| * <p> |
| * If a template name does not include a webapp name, then the template is created as a ContextHandler |
| * instead of a WebAppContext (with the exact type being determined by overlay.xml). |
| */ |
| public class OverlayedAppProvider extends AbstractLifeCycle implements AppProvider |
| { |
| private final static Logger __log=org.eclipse.jetty.util.log.Log.getLogger("OverlayedAppProvider"); |
| /** |
| * Property set for overlay.xml and template.xml files that gives the root overlay scan directory as a canonical file name. |
| */ |
| public final static String OVERLAYS_DIR="overlays.dir"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current webapp name, as {@link Webapp#getName()}. |
| */ |
| public final static String OVERLAY_WEBAPP="overlay.webapp"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current template full name, as {@link Template#getName()}. |
| */ |
| public final static String OVERLAY_TEMPLATE="overlay.template"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current template name, as {@link Template#getTemplateName()}. |
| */ |
| public final static String OVERLAY_TEMPLATE_NAME="overlay.template.name"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current template classifier, as {@link Template#getClassifier()}. |
| */ |
| public final static String OVERLAY_TEMPLATE_CLASSIFIER="overlay.template.classifier"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current node name, as {@link Node#getName()}. |
| */ |
| public final static String OVERLAY_NODE="overlay.node"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current instance name, {@link Instance#getName()}. |
| */ |
| public final static String OVERLAY_INSTANCE="overlay.instance"; |
| /** |
| * Property set for overlay.xml and template.xml files that gives the current instance clasifier, {@link Instance#getClassifier()}. |
| */ |
| public final static String OVERLAY_INSTANCE_CLASSIFIER="overlay.instance.classifier"; |
| |
| public final static String WEBAPPS="webapps"; |
| public final static String TEMPLATES="templates"; |
| public final static String NODES="nodes"; |
| public final static String INSTANCES="instances"; |
| |
| public final static String LIB="WEB-INF/lib-overlay"; |
| public final static String WEBAPP="."; |
| public final static String OVERLAY_XML="WEB-INF/overlay.xml"; |
| public final static String TEMPLATE_XML="WEB-INF/template.xml"; |
| public final static String WEB_DEFAULT_XML="WEB-INF/web-default.xml"; |
| public final static String WEB_FRAGMENT_XML="WEB-INF/web-overlay.xml"; |
| |
| enum Monitor { WEBAPPS,TEMPLATES,NODES,INSTANCES} ; |
| |
| public final static List<Pattern> __scanPatterns = new ArrayList<Pattern>(); |
| |
| static |
| { |
| List<String> regexes = new ArrayList<String>(); |
| |
| for (String s:new String[] {".war",".jar","/WEB-INF/syslib/[^/]*","/WEB-INF/lib/[^/]*","/WEB-INF/classes/[^/]*","/WEB-INF/[^/]*\\.xml",}) |
| { |
| regexes.add(WEBAPPS+"/[^/]*"+s); |
| regexes.add(TEMPLATES+"/[^/]*"+s); |
| regexes.add(NODES+"/[^/]*"+s); |
| regexes.add(INSTANCES+"/[^/]*"+s); |
| } |
| |
| for (String s: regexes) |
| __scanPatterns.add(Pattern.compile(s,Pattern.CASE_INSENSITIVE)); |
| }; |
| |
| private String _nodeName; |
| private File _scanDir; |
| private File _tmpDir; |
| private String _scanDirURI; |
| private long _loading; |
| private Node _node; |
| private final Map<String,Webapp> _webapps = new HashMap<String,Webapp>(); |
| private final Map<String,Template> _templates = new HashMap<String,Template>(); |
| private final Map<String,Instance> _instances = new HashMap<String,Instance>(); |
| private final Map<String,OverlayedApp> _deployed = new HashMap<String,OverlayedApp>(); |
| private final Map<String,TemplateContext> _shared = new HashMap<String, TemplateContext>(); |
| private boolean _copydir=false; |
| private DeploymentManager _deploymentManager; |
| private ConfigurationManager _configurationManager; |
| private String _serverID="Server"; |
| private final Set<Layer> _removedLayers = new HashSet<Layer>(); |
| private Timer _sessionScavenger = new Timer(); |
| |
| private final Scanner _scanner = new Scanner(); |
| private final Scanner.BulkListener _listener = new Scanner.BulkListener() |
| { |
| public void filesChanged(List<String> filenames) throws Exception |
| { |
| __log.debug("Changed {}",filenames); |
| |
| Set<String> changes = new HashSet<String>(); |
| for (String filename:filenames) |
| { |
| |
| File file=new File(filename); |
| if (file.getName().startsWith(".") || file.getName().endsWith(".swp")) |
| continue; |
| |
| String relname=file.toURI().getPath().substring(_scanDirURI.length()); |
| |
| File rel = new File(relname); |
| |
| String dir=null; |
| String name=null; |
| String parent=rel.getParent(); |
| while (parent!=null) |
| { |
| name=rel.getName(); |
| dir=parent; |
| rel=rel.getParentFile(); |
| parent=rel.getParent(); |
| } |
| |
| String uri=dir+"/"+name; |
| |
| for (Pattern p : __scanPatterns) |
| { |
| if (p.matcher(relname).matches()) |
| { |
| __log.debug("{} == {}",relname,p.pattern()); |
| changes.add(uri); |
| } |
| else |
| __log.debug("{} != {}",relname,p.pattern()); |
| } |
| } |
| |
| if (changes.size()>0) |
| OverlayedAppProvider.this.updateLayers(changes); |
| } |
| }; |
| |
| |
| /* ------------------------------------------------------------ */ |
| public OverlayedAppProvider() |
| { |
| try |
| { |
| _nodeName=InetAddress.getLocalHost().getHostName(); |
| } |
| catch(UnknownHostException e) |
| { |
| __log.debug(e); |
| _nodeName="unknown"; |
| } |
| } |
| |
| |
| |
| /* ------------------------------------------------------------ */ |
| public void setDeploymentManager(DeploymentManager deploymentManager) |
| { |
| _deploymentManager=deploymentManager; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public DeploymentManager getDeploymentManager() |
| { |
| return _deploymentManager; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public ConfigurationManager getConfigurationManager() |
| { |
| return _configurationManager; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Set the configurationManager. |
| * @param configurationManager the configurationManager to set |
| */ |
| public void setConfigurationManager(ConfigurationManager configurationManager) |
| { |
| _configurationManager = configurationManager; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @return The name in {@link XmlConfiguration#getIdMap()} of the {@link Server} instance. Default "Server". |
| */ |
| public String getServerID() |
| { |
| return _serverID; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @param serverID The name in {@link XmlConfiguration#getIdMap()} of the {@link Server} instance. |
| */ |
| public void setServerID(String serverID) |
| { |
| _serverID = serverID; |
| } |
| |
| |
| /** |
| * Create Context Handler. |
| * <p> |
| * Callback from the deployment manager to create a context handler instance. |
| * @see org.eclipse.jetty.deploy.AppProvider#createContextHandler(org.eclipse.jetty.deploy.App) |
| */ |
| public synchronized ContextHandler createContextHandler(App app) throws Exception |
| { |
| final OverlayedApp overlayed = (OverlayedApp)app; |
| final String origin = overlayed.getOriginId(); |
| final Instance instance = overlayed.getInstance(); |
| final Template template = instance.getTemplate(); |
| final Webapp webapp = template.getWebapp(); |
| final Node node = _node; |
| |
| // remember the original loader |
| ClassLoader orig_loader = Thread.currentThread().getContextClassLoader(); |
| try |
| { |
| // Look for existing shared resources |
| String key=(node==null?"":node.getLoadedKey())+template.getLoadedKey()+(webapp==null?"":webapp.getLoadedKey()); |
| instance.setSharedKey(key); |
| |
| TemplateContext shared=_shared.get(key); |
| // Create shared resourced |
| if (shared==null) |
| shared=createTemplateContext(key,webapp,template,node,orig_loader); |
| |
| // Build the instance lib loader |
| ClassLoader shared_loader = shared.getWebappLoader()!=null?shared.getWebappLoader():(shared.getLibLoader()!=null?shared.getLibLoader():orig_loader); |
| ClassLoader loader = shared_loader; |
| Resource instance_lib = instance.getResource(LIB); |
| if (instance_lib.exists()) |
| { |
| List<URL> libs = new ArrayList<URL>(); |
| for (String jar :instance_lib.list()) |
| { |
| if (!jar.toLowerCase(Locale.ENGLISH).endsWith(".jar")) |
| continue; |
| libs.add(instance_lib.addPath(jar).getURL()); |
| } |
| |
| __log.debug("{}: libs={}",origin,libs); |
| loader = URLClassLoader.newInstance(libs.toArray(new URL[]{}),loader); |
| } |
| |
| // set the thread loader |
| Thread.currentThread().setContextClassLoader(loader); |
| |
| // Create properties to be shared by overlay.xmls |
| Map<String,Object> idMap = new HashMap<String,Object>(); |
| idMap.putAll(shared.getIdMap()); |
| idMap.put(_serverID,getDeploymentManager().getServer()); |
| |
| // Create the instance context for the template |
| ContextHandler context=null; |
| |
| Resource template_context_xml = template.getResource(OVERLAY_XML); |
| if (template_context_xml.exists()) |
| { |
| __log.debug("{}: overlay.xml={}",origin,template_context_xml); |
| XmlConfiguration xmlc = newXmlConfiguration(template_context_xml.getURL(),idMap,template,instance); |
| context=(ContextHandler)xmlc.configure(); |
| idMap=xmlc.getIdMap(); |
| } |
| else if (webapp==null) |
| // If there is no webapp, this is a plain context |
| context=new ContextHandler(); |
| else |
| // It is a webapp context |
| context=new WebAppContext(); |
| |
| // Set the resource base |
| final Resource instance_webapp = instance.getResource(WEBAPP); |
| if (instance_webapp.exists()) |
| { |
| context.setBaseResource(new ResourceCollection(instance_webapp,shared.getBaseResource())); |
| |
| // Create the resource cache |
| ResourceCache cache = new ResourceCache(shared.getResourceCache(),instance_webapp,context.getMimeTypes(),false,false); |
| context.setAttribute(ResourceCache.class.getCanonicalName(),cache); |
| } |
| else |
| { |
| context.setBaseResource(shared.getBaseResource()); |
| context.setAttribute(ResourceCache.class.getCanonicalName(),shared.getResourceCache()); |
| } |
| __log.debug("{}: baseResource={}",origin,context.getResourceBase()); |
| |
| // Set the shared session scavenger timer |
| context.setAttribute("org.eclipse.jetty.server.session.timer", _sessionScavenger); |
| |
| // Apply any node or instance overlay.xml |
| for (Resource context_xml : getLayeredResources(OVERLAY_XML,node,instance)) |
| { |
| __log.debug("{}: overlay.xml={}",origin,context_xml); |
| XmlConfiguration xmlc = newXmlConfiguration(context_xml.getURL(),idMap,template,instance); |
| xmlc.getIdMap().put("Cache",context.getAttribute(ResourceCache.class.getCanonicalName())); |
| xmlc.configure(context); |
| idMap=xmlc.getIdMap(); |
| } |
| |
| // Is it a webapp? |
| if (context instanceof WebAppContext) |
| { |
| final WebAppContext webappcontext = (WebAppContext)context; |
| |
| if (Arrays.asList(((WebAppContext)context).getServerClasses()).toString().equals(Arrays.asList(WebAppContext.__dftServerClasses).toString())) |
| { |
| __log.debug("clear server classes"); |
| webappcontext.setServerClasses(null); |
| } |
| |
| // set classloader |
| webappcontext.setCopyWebDir(false); |
| webappcontext.setCopyWebInf(false); |
| webappcontext.setExtractWAR(false); |
| |
| if (instance_webapp.exists()) |
| { |
| final Resource classes=instance_webapp.addPath("WEB-INF/classes"); |
| final Resource lib=instance_webapp.addPath("WEB-INF/lib"); |
| |
| if (classes.exists()||lib.exists()) |
| { |
| final AtomicBoolean locked =new AtomicBoolean(false); |
| |
| WebAppClassLoader webapp_loader=new WebAppClassLoader(loader,webappcontext) |
| { |
| @Override |
| public void addClassPath(Resource resource) throws IOException |
| { |
| if (!locked.get()) |
| super.addClassPath(resource); |
| } |
| |
| @Override |
| public void addClassPath(String classPath) throws IOException |
| { |
| if (!locked.get()) |
| super.addClassPath(classPath); |
| } |
| |
| @Override |
| public void addJars(Resource lib) |
| { |
| if (!locked.get()) |
| super.addJars(lib); |
| } |
| }; |
| |
| if (classes.exists()) |
| webapp_loader.addClassPath(classes); |
| if (lib.exists()) |
| webapp_loader.addJars(lib); |
| locked.set(true); |
| |
| loader=webapp_loader; |
| } |
| } |
| |
| // Make sure loader is unique for JNDI |
| if (loader==shared_loader) |
| loader = new URLClassLoader(new URL[]{},shared_loader); |
| |
| // add default descriptor |
| List<Resource> webdefaults=getLayeredResources(WEB_DEFAULT_XML,instance,node,template); |
| if (webdefaults.size()>0) |
| { |
| Resource webdefault = webdefaults.get(0); |
| __log.debug("{}: defaultweb={}",origin,webdefault); |
| webappcontext.setDefaultsDescriptor(webdefault.toString()); |
| } |
| |
| // add overlay descriptors |
| for (Resource override : getLayeredResources(WEB_FRAGMENT_XML,template,node,instance)) |
| { |
| __log.debug("{}: web override={}",origin,override); |
| webappcontext.addOverrideDescriptor(override.toString()); |
| } |
| } |
| |
| context.setClassLoader(loader); |
| |
| __log.debug("{}: baseResource={}",origin,context.getBaseResource()); |
| |
| Resource jetty_web_xml = context.getResource("/WEB-INF/"+JettyWebXmlConfiguration.JETTY_WEB_XML); |
| if (jetty_web_xml!=null && jetty_web_xml.exists()) |
| context.setAttribute(JettyWebXmlConfiguration.XML_CONFIGURATION,newXmlConfiguration(jetty_web_xml.getURL(),idMap,template,instance)); |
| |
| // Add listener to expand parameters from descriptors before other listeners execute |
| Map<String,String> params = new HashMap<String,String>(); |
| populateParameters(params,template,instance); |
| context.addEventListener(new ParameterExpander(params,context)); |
| |
| System.err.println("created:\n"+context.dump()); |
| |
| return context; |
| } |
| finally |
| { |
| Thread.currentThread().setContextClassLoader(orig_loader); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private XmlConfiguration newXmlConfiguration(URL url, Map<String, Object> idMap, Template template, Instance instance) throws SAXException, IOException |
| { |
| XmlConfiguration xmlc = new XmlConfiguration(url); |
| populateParameters(xmlc.getProperties(),template,instance); |
| xmlc.getIdMap().putAll(idMap); |
| |
| return xmlc; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private void populateParameters(Map<String,String> params,Template template, Instance instance) |
| { |
| try |
| { |
| params.put(OVERLAYS_DIR,_scanDir.getCanonicalPath()); |
| if (template!=null) |
| { |
| params.put(OVERLAY_TEMPLATE,template.getName()); |
| params.put(OVERLAY_TEMPLATE_NAME,template.getTemplateName()); |
| params.put(OVERLAY_TEMPLATE_CLASSIFIER,template.getClassifier()); |
| params.put(OVERLAY_WEBAPP,template.getWebapp()==null?null:template.getWebapp().getName()); |
| } |
| if (_node!=null) |
| params.put(OVERLAY_NODE,_node.getName()); |
| if (instance!=null) |
| { |
| params.put(OVERLAY_INSTANCE,instance.getName()); |
| params.put(OVERLAY_INSTANCE_CLASSIFIER,instance.getClassifier()); |
| } |
| if (getConfigurationManager()!=null) |
| params.putAll(getConfigurationManager().getProperties()); |
| } |
| catch(Exception e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| private TemplateContext createTemplateContext(final String key, Webapp webapp, Template template, Node node, ClassLoader parent) throws Exception |
| { |
| __log.info("created {}",key); |
| |
| // look for libs |
| // If we have libs directories, create classloader and make it available to |
| // the XMLconfiguration |
| List<URL> libs = new ArrayList<URL>(); |
| for (Resource lib : getLayeredResources(LIB,node,template)) |
| { |
| for (String jar :lib.list()) |
| { |
| if (!jar.toLowerCase(Locale.ENGLISH).endsWith(".jar")) |
| continue; |
| libs.add(lib.addPath(jar).getURL()); |
| } |
| } |
| final ClassLoader libLoader; |
| if (libs.size()>0) |
| { |
| __log.debug("{}: libs={}",key,libs); |
| libLoader=new URLClassLoader(libs.toArray(new URL[]{}),parent) |
| { |
| public String toString() {return "libLoader@"+Long.toHexString(hashCode())+"-lib-"+key;} |
| }; |
| |
| } |
| else |
| libLoader=parent; |
| |
| Thread.currentThread().setContextClassLoader(libLoader); |
| |
| |
| // Make the shared resourceBase |
| List<Resource> bases = new ArrayList<Resource>(); |
| for (Resource wa : getLayers(node,template)) |
| bases.add(wa); |
| if (webapp!=null) |
| bases.add(webapp.getBaseResource()); |
| Resource baseResource = bases.size()==1?bases.get(0):new ResourceCollection(bases.toArray(new Resource[bases.size()])); |
| __log.debug("{}: baseResource={}",key,baseResource); |
| |
| |
| // Make the shared context |
| TemplateContext shared = new TemplateContext(key,getDeploymentManager().getServer(),baseResource,libLoader); |
| _shared.put(key,shared); |
| |
| |
| // Create properties to be shared by overlay.xmls |
| Map<String,Object> idMap = new HashMap<String,Object>(); |
| idMap.put(_serverID,getDeploymentManager().getServer()); |
| |
| |
| // Create the shared context for the template |
| // This instance will never be start, but is used to capture the |
| // shared results of running the template and node overlay.xml files. |
| // If there is a template overlay.xml, give it the chance to create the ContextHandler instance |
| // otherwise create an instance ourselves |
| for (Resource template_xml : getLayeredResources(TEMPLATE_XML,template,node)) |
| { |
| __log.debug("{}: template.xml={}",key,template_xml); |
| XmlConfiguration xmlc = newXmlConfiguration(template_xml.getURL(),idMap,template,null); |
| xmlc.getIdMap().putAll(idMap); |
| xmlc.configure(shared); |
| idMap=xmlc.getIdMap(); |
| } |
| |
| shared.setIdMap(idMap); |
| shared.start(); |
| |
| return shared; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @return The node name (defaults to hostname) |
| */ |
| public String getNodeName() |
| { |
| return _nodeName; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @param nodeName Set the node name |
| */ |
| public void setNodeName(String nodeName) |
| { |
| _nodeName = nodeName; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Get the scanDir. |
| * @return the scanDir |
| */ |
| public File getScanDir() |
| { |
| return _scanDir; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Set the scanDir. |
| * @param scanDir the scanDir to set |
| */ |
| public void setScanDir(File scanDir) |
| { |
| _scanDir = scanDir; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Set the temporary directory. |
| * @param tmpDir the directory for temporary files. If null, then getScanDir()+"/tmp" is used if it exists, else the system default is used. |
| */ |
| public void setTmpDir(File tmpDir) |
| { |
| _tmpDir=tmpDir; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Get the temporary directory. |
| * return the tmpDir. If null, then getScanDir()+"/tmp" is used if it exists, else the system default is used. |
| */ |
| public File getTmpDir() |
| { |
| return _tmpDir; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @return The scan interval |
| * @see org.eclipse.jetty.util.Scanner#getScanInterval() |
| */ |
| public int getScanInterval() |
| { |
| return _scanner.getScanInterval(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @param scanInterval The scan interval |
| * @see org.eclipse.jetty.util.Scanner#setScanInterval(int) |
| */ |
| public void setScanInterval(int scanInterval) |
| { |
| _scanner.setScanInterval(scanInterval); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @see org.eclipse.jetty.util.Scanner#scan() |
| */ |
| public void scan() |
| { |
| _scanner.scan(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() |
| */ |
| @Override |
| protected void doStart() throws Exception |
| { |
| __log.info("Node={} Scan=",_nodeName,_scanDir); |
| if (_scanDir==null || !_scanDir.exists()) |
| throw new IllegalStateException("!scandir"); |
| |
| _scanDirURI=_scanDir.toURI().getPath(); |
| _scanner.setScanDepth(6); // enough for templates/name/webapps/WEB-INF/lib/foo.jar |
| List<File> dirs = Arrays.asList(new File[] |
| { |
| new File(_scanDir,WEBAPPS), |
| new File(_scanDir,TEMPLATES), |
| new File(_scanDir,NODES), |
| new File(_scanDir,INSTANCES) |
| }); |
| for (File file : dirs) |
| { |
| if (!file.exists() && !file.isDirectory()) |
| __log.warn("No directory: "+file.getAbsolutePath()); |
| } |
| _scanner.setScanDirs(dirs); |
| _scanner.addListener(_listener); |
| _scanner.start(); |
| |
| super.doStart(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() |
| */ |
| @Override |
| protected void doStop() throws Exception |
| { |
| _scanner.removeListener(_listener); |
| _scanner.stop(); |
| |
| if (_deploymentManager.isRunning()) |
| { |
| for (App app: _deployed.values()) |
| _deploymentManager.removeApp(app); |
| } |
| _deployed.clear(); |
| |
| for (Layer layer : _webapps.values()) |
| layer.release(); |
| _webapps.clear(); |
| for (Layer layer : _templates.values()) |
| layer.release(); |
| _templates.clear(); |
| if (_node!=null) |
| _node.release(); |
| for (Layer layer : _instances.values()) |
| layer.release(); |
| _instances.clear(); |
| |
| super.doStop(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected synchronized void updateLayers(Set<String> layerURIs) |
| { |
| _loading=System.currentTimeMillis(); |
| for (String ruri: layerURIs) |
| { |
| try |
| { |
| // Decompose the name |
| File directory; |
| File archive; |
| File origin = new File(new URI(_scanDir.toURI()+ruri)); |
| String name=origin.getName(); |
| |
| Monitor monitor = Monitor.valueOf(origin.getParentFile().getName().toUpperCase(Locale.ENGLISH)); |
| |
| String ext=".war"; |
| |
| // check directory vs archive |
| if (origin.isDirectory() || !origin.exists() && !ruri.toLowerCase(Locale.ENGLISH).endsWith(ext)) |
| { |
| // directories have priority over archives |
| directory=origin; |
| archive=new File(directory.toString()+ext); |
| } |
| else |
| { |
| // check extension name |
| if (!ruri.toLowerCase(Locale.ENGLISH).endsWith(ext)) |
| continue; |
| |
| name=name.substring(0,name.length()-4); |
| archive=origin; |
| directory=new File(new URI(_scanDir.toURI()+ruri.substring(0,ruri.length()-4))); |
| |
| // Look to see if directory exists |
| if (directory.exists()) |
| { |
| __log.info("Directory exists, ignoring change to {}",ruri); |
| continue; |
| } |
| } |
| |
| Layer layer=null; |
| |
| switch(monitor) |
| { |
| case WEBAPPS: |
| if (origin.exists()) |
| layer=loadWebapp(name,origin); |
| else |
| { |
| removeWebapp(name); |
| if (origin==directory && archive.exists()) |
| layer=loadWebapp(name,archive); |
| } |
| |
| break; |
| |
| case TEMPLATES: |
| if (origin.exists()) |
| layer=loadTemplate(name,origin); |
| else |
| { |
| removeTemplate(name); |
| if (origin==directory && archive.exists()) |
| layer=loadTemplate(name,archive); |
| } |
| break; |
| |
| case NODES: |
| if (name.equalsIgnoreCase(_nodeName)) |
| { |
| if (origin.exists()) |
| layer=loadNode(origin); |
| else |
| { |
| removeNode(); |
| if (origin==directory && archive.exists()) |
| layer=loadNode(archive); |
| } |
| } |
| break; |
| |
| case INSTANCES: |
| if (origin.exists()) |
| layer=loadInstance(name,origin); |
| else |
| { |
| removeInstance(name); |
| if (origin==directory && archive.exists()) |
| layer=loadInstance(name,archive); |
| } |
| break; |
| |
| } |
| |
| if (layer!=null) |
| __log.info("loaded {}",layer.getLoadedKey()); |
| } |
| catch(Exception e) |
| { |
| __log.warn(e); |
| } |
| } |
| |
| redeploy(); |
| |
| // Release removed layers |
| for (Layer layer : _removedLayers) |
| { |
| if (layer!=null) |
| { |
| __log.info("unload {}",layer.getLoadedKey()); |
| layer.release(); |
| } |
| } |
| _removedLayers.clear(); |
| |
| if (__log.isDebugEnabled()) |
| { |
| System.err.println("updated:"); |
| System.err.println("java:"+javaRootURLContext.getRoot().dump()); |
| System.err.println("local:"+localContextRoot.getRoot().dump()); |
| if (getDeploymentManager()!=null && getDeploymentManager().getServer()!=null) |
| System.err.println(getDeploymentManager().getServer().dump()); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected File tmpdir(String name,String suffix) throws IOException |
| { |
| File dir=_tmpDir; |
| if (dir==null || !dir.isDirectory() || !dir.canWrite()) |
| { |
| dir=new File(_scanDir,"tmp"); |
| if (!dir.isDirectory() || !dir.canWrite()) |
| dir=null; |
| } |
| |
| File tmp = File.createTempFile(name+"_","."+suffix,dir); |
| tmp=tmp.getCanonicalFile(); |
| if (tmp.exists()) |
| IO.delete(tmp); |
| tmp.mkdir(); |
| tmp.deleteOnExit(); |
| return tmp; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Walks the defined webapps, templates, nodes and instances to |
| * determine what should be deployed, then adjust reality to match. |
| */ |
| protected void redeploy() |
| { |
| Map<String,Template> templates = new ConcurrentHashMap<String,Template>(); |
| |
| // Check for duplicate templates |
| for (Template template : _templates.values()) |
| { |
| Template other=templates.get(template.getTemplateName()); |
| if (other!=null) |
| { |
| __log.warn("Multiple Templates: {} & {}",template.getName(),other.getName()); |
| if (other.getName().compareToIgnoreCase(template.getName())<=0) |
| continue; |
| } |
| templates.put(template.getTemplateName(),template); |
| } |
| |
| // Match webapps to templates |
| for (Template template : templates.values()) |
| { |
| String webappname=template.getClassifier(); |
| |
| if (webappname==null) |
| continue; |
| |
| Webapp webapp = _webapps.get(webappname); |
| |
| if (webapp==null) |
| { |
| __log.warn("No webapp found for template: {}",template.getName()); |
| templates.remove(template.getTemplateName()); |
| } |
| else |
| { |
| template.setWebapp(webapp); |
| } |
| } |
| |
| // Match instance to templates and check if what needs to be deployed or undeployed. |
| Set<String> deployed = new HashSet<String>(); |
| List<Instance> deploy = new ArrayList<Instance>(); |
| |
| for (Instance instance : _instances.values()) |
| { |
| Template template=templates.get(instance.getTemplateName()); |
| instance.setTemplate(template); |
| if (template!=null) |
| { |
| String key=instance.getInstanceKey(); |
| App app = _deployed.get(key); |
| if (app==null) |
| deploy.add(instance); |
| else |
| deployed.add(key); |
| } |
| } |
| |
| // Look for deployed apps that need to be undeployed |
| List<String> undeploy = new ArrayList<String>(); |
| for (String key : _deployed.keySet()) |
| { |
| if (!deployed.contains(key)) |
| undeploy.add(key); |
| } |
| |
| // Do the undeploys |
| for (String key : undeploy) |
| { |
| App app = _deployed.remove(key); |
| if (app!=null) |
| { |
| __log.info("Undeploy {}",key); |
| _deploymentManager.removeApp(app); |
| } |
| } |
| |
| // ready the deploys |
| for (Instance instance : deploy) |
| { |
| String key=instance.getInstanceKey(); |
| OverlayedApp app = new OverlayedApp(_deploymentManager,this,key,instance); |
| _deployed.put(key,app); |
| } |
| |
| // Remove unused Shared stuff |
| Set<String> sharedKeys = new HashSet<String>(_shared.keySet()); |
| for (OverlayedApp app : _deployed.values()) |
| { |
| Instance instance = app.getInstance(); |
| sharedKeys.remove(instance.getSharedKey()); |
| } |
| for (String sharedKey: sharedKeys) |
| { |
| __log.debug("Remove "+sharedKey); |
| TemplateContext shared=_shared.remove(sharedKey); |
| if (shared!=null) |
| { |
| try |
| { |
| shared.stop(); |
| } |
| catch(Exception e) |
| { |
| __log.warn(e); |
| } |
| shared.destroy(); |
| } |
| } |
| |
| // Do the deploys |
| for (Instance instance : deploy) |
| { |
| String key=instance.getInstanceKey(); |
| OverlayedApp app = _deployed.get(key); |
| __log.info("Deploy {}",key); |
| _deploymentManager.addApp(app); |
| } |
| |
| |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected void removeInstance(String name) |
| { |
| _removedLayers.add(_instances.remove(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected Instance loadInstance(String name, File origin) |
| throws IOException |
| { |
| Instance instance=new Instance(name,origin); |
| _removedLayers.add(_instances.put(name,instance)); |
| return instance; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected void removeNode() |
| { |
| if (_node!=null) |
| _removedLayers.add(_node); |
| _node=null; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected Node loadNode(File origin) |
| throws IOException |
| { |
| if (_node!=null) |
| _removedLayers.add(_node); |
| _node=new Node(_nodeName,origin); |
| return _node; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected void removeTemplate(String name) |
| { |
| _removedLayers.add(_templates.remove(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected Template loadTemplate(String name, File origin) |
| throws IOException |
| { |
| Template template=new Template(name,origin); |
| _removedLayers.add(_templates.put(name,template)); |
| return template; |
| } |
| |
| protected void removeWebapp(String name) |
| { |
| _removedLayers.add(_webapps.remove(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| protected Webapp loadWebapp(String name, File origin) |
| throws IOException |
| { |
| Webapp webapp = new Webapp(name,origin); |
| _removedLayers.add(_webapps.put(name,webapp)); |
| return webapp; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private static List<Resource> getLayers(Layer... layers) |
| { |
| List<Resource> resources = new ArrayList<Resource>(); |
| for (Layer layer: layers) |
| { |
| if (layer==null) |
| continue; |
| Resource resource = layer.getBaseResource(); |
| if (resource.exists()) |
| resources.add(resource); |
| } |
| return resources; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private static List<Resource> getLayeredResources(String path, Layer... layers) |
| { |
| List<Resource> resources = new ArrayList<Resource>(); |
| for (Layer layer: layers) |
| { |
| if (layer==null) |
| continue; |
| Resource resource = layer.getResource(path); |
| if (resource.exists()) |
| resources.add(resource); |
| } |
| return resources; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| class Layer |
| { |
| private final String _name; |
| private final File _origin; |
| private final long _loaded=_loading; |
| private final Resource _resourceBase; |
| private final boolean _resourceBaseIsCopy; |
| |
| public Layer(String name, File origin) |
| throws IOException |
| { |
| super(); |
| _name = name; |
| _origin = origin; |
| |
| Resource resource = Resource.newResource(origin.toURI()); |
| |
| if (resource.isDirectory()) |
| { |
| if (_copydir) |
| { |
| File tmp=tmpdir(name,"extract"); |
| __log.info("Extract {} to {}",origin,tmp); |
| IO.copyDir(origin,tmp); |
| _resourceBase=Resource.newResource(tmp.toURI()); |
| _resourceBaseIsCopy=true; |
| } |
| else |
| { |
| _resourceBase=resource; |
| _resourceBaseIsCopy=false; |
| } |
| } |
| else |
| { |
| Resource jar = JarResource.newJarResource(resource); |
| File tmp=tmpdir(name,"extract"); |
| __log.info("Extract {} to {}",jar,tmp); |
| jar.copyTo(tmp); |
| _resourceBase=Resource.newResource(tmp.toURI()); |
| _resourceBaseIsCopy=true; |
| } |
| } |
| |
| public String getName() |
| { |
| return _name; |
| } |
| |
| public File getOrigin() |
| { |
| return _origin; |
| } |
| |
| public long getLoaded() |
| { |
| return _loaded; |
| } |
| |
| public Resource getBaseResource() |
| { |
| return _resourceBase; |
| } |
| |
| public Resource getResource(String path) |
| { |
| try |
| { |
| return getBaseResource().addPath(path); |
| } |
| catch(Exception e) |
| { |
| __log.warn(e); |
| } |
| return null; |
| } |
| |
| public String getLoadedKey() |
| { |
| return _name+"@"+_loaded; |
| } |
| |
| public void release() |
| { |
| if (_resourceBaseIsCopy) |
| { |
| try |
| { |
| File file = _resourceBase.getFile(); |
| if (file!=null) |
| IO.delete(file); |
| } |
| catch(Exception e) |
| { |
| __log.warn(e); |
| } |
| } |
| } |
| |
| public String toString() |
| { |
| return getLoadedKey(); |
| } |
| } |
| |
| class Webapp extends Layer |
| { |
| public Webapp(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| } |
| } |
| |
| class Overlay extends Layer |
| { |
| public Overlay(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| } |
| |
| public Resource getContext() |
| { |
| return getResource(OVERLAY_XML); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| class Node extends Overlay |
| { |
| public Node(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| } |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| class ClassifiedOverlay extends Overlay |
| { |
| private final String _templateName; |
| private final String _classifier; |
| |
| public ClassifiedOverlay(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| |
| int l=1; |
| int e=name.indexOf('='); |
| if (e<0) |
| { |
| l=2; |
| e=name.indexOf("--"); |
| } |
| _templateName=e>=0?name.substring(0,e):name; |
| _classifier=e>=0?name.substring(e+l):null; |
| } |
| |
| public String getTemplateName() |
| { |
| return _templateName; |
| } |
| |
| public String getClassifier() |
| { |
| return _classifier; |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| class Template extends ClassifiedOverlay |
| { |
| private Webapp _webapp; |
| |
| public Webapp getWebapp() |
| { |
| return _webapp; |
| } |
| |
| public void setWebapp(Webapp webapp) |
| { |
| _webapp = webapp; |
| } |
| |
| public Template(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| class Instance extends ClassifiedOverlay |
| { |
| Template _template; |
| String _sharedKey; |
| |
| public Instance(String name, File origin) throws IOException |
| { |
| super(name,origin); |
| if (getClassifier()==null) |
| throw new IllegalArgumentException("Instance without '=':"+name); |
| } |
| |
| public void setSharedKey(String key) |
| { |
| _sharedKey=key; |
| } |
| |
| public String getSharedKey() |
| { |
| return _sharedKey; |
| } |
| |
| public void setTemplate(Template template) |
| { |
| _template=template; |
| } |
| |
| public Template getTemplate() |
| { |
| return _template; |
| } |
| |
| public String getInstanceKey() |
| { |
| return |
| (_template.getWebapp()==null?"":_template.getWebapp().getLoadedKey())+"|"+ |
| _template.getLoadedKey()+"|"+ |
| (_node==null?"":_node.getLoadedKey())+"|"+ |
| getLoadedKey(); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| static class OverlayedApp extends App |
| { |
| final Instance _instance; |
| |
| public OverlayedApp(DeploymentManager manager, AppProvider provider, String originId, Instance instance) |
| { |
| super(manager,provider,originId); |
| _instance=instance; |
| } |
| |
| public Instance getInstance() |
| { |
| return _instance; |
| } |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| private final class ParameterExpander implements ServletContextListener |
| { |
| private final Map<String, String> _params; |
| private final ContextHandler _ctx; |
| |
| private ParameterExpander(Map<String, String> params, ContextHandler ctx) |
| { |
| _params = params; |
| _ctx = ctx; |
| } |
| |
| public void contextInitialized(ServletContextEvent sce) |
| { |
| Enumeration<String> e=_ctx.getInitParameterNames(); |
| while (e.hasMoreElements()) |
| { |
| String name = e.nextElement(); |
| _ctx.setInitParameter(name,expandParameter(_ctx.getInitParameter(name))); |
| } |
| |
| ServletHandler servletHandler = _ctx.getChildHandlerByClass(ServletHandler.class); |
| if (servletHandler!=null) |
| { |
| List<Holder<?>> holders = new ArrayList<Holder<?>>(); |
| if (servletHandler.getFilters()!=null) |
| holders.addAll(Arrays.asList(servletHandler.getFilters())); |
| if (servletHandler.getHandler()!=null) |
| holders.addAll(Arrays.asList(servletHandler.getServlets())); |
| for (Holder<?> holder: holders) |
| { |
| e=holder.getInitParameterNames(); |
| while (e.hasMoreElements()) |
| { |
| String name = e.nextElement(); |
| holder.setInitParameter(name,expandParameter(holder.getInitParameter(name))); |
| } |
| } |
| } |
| } |
| |
| private String expandParameter(String value) |
| { |
| int i=0; |
| while (true) |
| { |
| int open=value.indexOf("${",i); |
| if (open<0) |
| return value; |
| int close=value.indexOf("}",open); |
| if (close<0) |
| return value; |
| |
| String param = value.substring(open+2,close); |
| if (_params.containsKey(param)) |
| { |
| String tmp=value.substring(0,open)+_params.get(param); |
| i=tmp.length(); |
| value=tmp+value.substring(close+1); |
| } |
| else |
| i=close+1; |
| } |
| } |
| |
| public void contextDestroyed(ServletContextEvent sce) |
| { |
| } |
| } |
| } |