View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.webapp;
20  
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.net.JarURLConnection;
25  import java.net.URI;
26  import java.net.URL;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.concurrent.ConcurrentHashMap;
35  import java.util.jar.JarEntry;
36  import java.util.jar.JarFile;
37  
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.log.Logger;
40  import org.eclipse.jetty.util.resource.EmptyResource;
41  import org.eclipse.jetty.util.resource.Resource;
42  
43  /**
44   * MetaInfConfiguration
45   * <p>
46   *
47   * Scan META-INF of jars to find:
48   * <ul>
49   * <li>tlds</li>
50   * <li>web-fragment.xml</li>
51   * <li>resources</li>
52   * </ul>
53   * 
54   * The jars which are scanned are:
55   * <ol>
56   * <li>those from the container classpath whose pattern matched the WebInfConfiguration.CONTAINER_JAR_PATTERN</li>
57   * <li>those from WEB-INF/lib</li>
58   * </ol>
59   */
60  public class MetaInfConfiguration extends AbstractConfiguration
61  {
62      private static final Logger LOG = Log.getLogger(MetaInfConfiguration.class);
63  
64      public static final String USE_CONTAINER_METAINF_CACHE = "org.eclipse.jetty.metainf.useCache";
65      public static final boolean DEFAULT_USE_CONTAINER_METAINF_CACHE = true;
66      public static final String CACHED_CONTAINER_TLDS = "org.eclipse.jetty.tlds.cache";
67      public static final String CACHED_CONTAINER_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES+".cache";
68      public static final String CACHED_CONTAINER_RESOURCES = WebInfConfiguration.RESOURCE_DIRS+".cache";
69      public static final String METAINF_TLDS = "org.eclipse.jetty.tlds";
70      public static final String METAINF_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES;
71      public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_DIRS;
72  
73      @Override
74      public void preConfigure(final WebAppContext context) throws Exception
75      {        
76          boolean useContainerCache = DEFAULT_USE_CONTAINER_METAINF_CACHE;
77          Boolean attr = (Boolean)context.getServer().getAttribute(USE_CONTAINER_METAINF_CACHE);
78          if (attr != null)
79              useContainerCache = attr.booleanValue();
80          
81          if (LOG.isDebugEnabled()) LOG.debug("{} = {}", USE_CONTAINER_METAINF_CACHE, useContainerCache);
82          
83          //pre-emptively create empty lists for tlds, fragments and resources as context attributes
84          //this signals that this class has been called. This differentiates the case where this class
85          //has been called but finds no META-INF data from the case where this class was never called
86          if (context.getAttribute(METAINF_TLDS) == null)
87              context.setAttribute(METAINF_TLDS, new HashSet<URL>());
88          if (context.getAttribute(METAINF_RESOURCES) == null)
89              context.setAttribute(METAINF_RESOURCES, new HashSet<Resource>());
90          if (context.getAttribute(METAINF_FRAGMENTS) == null)
91              context.setAttribute(METAINF_FRAGMENTS, new HashMap<Resource, Resource>());
92         
93          scanJars(context, context.getMetaData().getContainerResources(), useContainerCache);
94          scanJars(context, context.getMetaData().getWebInfJars(), false);
95      }
96  
97      /**
98       * Look into the jars to discover info in META-INF. If useCaches == true, then we will
99       * cache the info discovered indexed by the jar in which it was discovered: this speeds
100      * up subsequent context deployments.
101      * 
102      * @param context the context for the scan
103      * @param jars the jars resources to scan
104      * @param useCaches if true, cache the info discovered
105      * @throws Exception if unable to scan the jars
106      */
107     public void scanJars (final WebAppContext context, Collection<Resource> jars, boolean useCaches)
108     throws Exception
109     {
110         ConcurrentHashMap<Resource, Resource> metaInfResourceCache = null;       
111         ConcurrentHashMap<Resource, Resource> metaInfFragmentCache = null;
112         ConcurrentHashMap<Resource, Collection<URL>> metaInfTldCache = null;
113         if (useCaches)
114         {
115             metaInfResourceCache = (ConcurrentHashMap<Resource, Resource>)context.getServer().getAttribute(CACHED_CONTAINER_RESOURCES);
116             if (metaInfResourceCache == null)
117             {
118                 metaInfResourceCache = new ConcurrentHashMap<Resource,Resource>();
119                 context.getServer().setAttribute(CACHED_CONTAINER_RESOURCES, metaInfResourceCache);
120             }
121             metaInfFragmentCache = (ConcurrentHashMap<Resource, Resource>)context.getServer().getAttribute(CACHED_CONTAINER_FRAGMENTS);
122             if (metaInfFragmentCache == null)
123             {
124                 metaInfFragmentCache = new ConcurrentHashMap<Resource,Resource>();
125                 context.getServer().setAttribute(CACHED_CONTAINER_FRAGMENTS, metaInfFragmentCache);
126             }
127             metaInfTldCache = (ConcurrentHashMap<Resource, Collection<URL>>)context.getServer().getAttribute(CACHED_CONTAINER_TLDS);
128             if (metaInfTldCache == null)
129             {
130                 metaInfTldCache = new ConcurrentHashMap<Resource,Collection<URL>>(); 
131                 context.getServer().setAttribute(CACHED_CONTAINER_TLDS, metaInfTldCache);
132             }
133         }
134         
135         //Scan jars for META-INF information
136         if (jars != null)
137         {
138             for (Resource r : jars)
139             {
140                 
141                scanForResources(context, r, metaInfResourceCache);
142                scanForFragment(context, r, metaInfFragmentCache);
143                scanForTlds(context, r, metaInfTldCache);
144             }
145         }
146     }
147     
148     /**
149      * Scan for META-INF/resources dir in the given jar.
150      * 
151      * @param context the context for the scan
152      * @param target the target resource to scan for
153      * @param cache the resource cache
154      * @throws Exception if unable to scan for resources
155      */
156     public void scanForResources (WebAppContext context, Resource target, ConcurrentHashMap<Resource,Resource> cache)
157     throws Exception
158     {
159         Resource resourcesDir = null;
160         if (cache != null && cache.containsKey(target))
161         {
162             resourcesDir = cache.get(target);  
163             if (resourcesDir == EmptyResource.INSTANCE)
164             {
165                 if (LOG.isDebugEnabled()) LOG.debug(target+" cached as containing no META-INF/resources");
166                 return;    
167             }
168             else
169                 if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources found in cache ");
170         }
171         else
172         {
173             //not using caches or not in the cache so check for the resources dir
174             if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources checked");
175             if (target.isDirectory())
176             {
177                 //TODO think  how to handle an unpacked jar file (eg for osgi)
178                 resourcesDir = target.addPath("/META-INF/resources");
179             }
180             else
181             {
182                 //Resource represents a packed jar
183                 URI uri = target.getURI();
184                 resourcesDir = Resource.newResource("jar:"+uri+"!/META-INF/resources");
185             }
186             
187             if (!resourcesDir.exists() || !resourcesDir.isDirectory())
188             {
189                 resourcesDir.close();
190                 resourcesDir = EmptyResource.INSTANCE;
191             }
192 
193             if (cache != null)
194             {               
195                 Resource old  = cache.putIfAbsent(target, resourcesDir);
196                 if (old != null)
197                     resourcesDir = old;
198                 else
199                     if (LOG.isDebugEnabled()) LOG.debug(target+" META-INF/resources cache updated");
200             }
201 
202             if (resourcesDir == EmptyResource.INSTANCE)
203             {
204                 return;
205             }
206         }
207 
208         //add it to the meta inf resources for this context
209         Set<Resource> dirs = (Set<Resource>)context.getAttribute(METAINF_RESOURCES);
210         if (dirs == null)
211         {
212             dirs = new HashSet<Resource>();
213             context.setAttribute(METAINF_RESOURCES, dirs);
214         }
215         if (LOG.isDebugEnabled()) LOG.debug(resourcesDir+" added to context");
216 
217         dirs.add(resourcesDir);
218     }
219     
220     /**
221      * Scan for META-INF/web-fragment.xml file in the given jar.
222      * 
223      * @param context the context for the scan
224      * @param jar the jar resource to scan for fragements in
225      * @param cache the resource cache
226      * @throws Exception if unable to scan for fragments
227      */
228     public void scanForFragment (WebAppContext context, Resource jar, ConcurrentHashMap<Resource,Resource> cache)
229     throws Exception
230     {
231         Resource webFrag = null;
232         if (cache != null && cache.containsKey(jar))
233         {
234             webFrag = cache.get(jar);  
235             if (webFrag == EmptyResource.INSTANCE)
236             {
237                 if (LOG.isDebugEnabled()) LOG.debug(jar+" cached as containing no META-INF/web-fragment.xml");
238                 return;     
239             }
240             else
241                 if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml found in cache ");
242         }
243         else
244         {
245             //not using caches or not in the cache so check for the web-fragment.xml
246             if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml checked");
247             if (jar.isDirectory())
248             {
249                 //TODO   ????
250                 webFrag = jar.addPath("/META-INF/web-fragment.xml");
251             }
252             else
253             {
254                 URI uri = jar.getURI();
255                 webFrag = Resource.newResource("jar:"+uri+"!/META-INF/web-fragment.xml");
256             }
257             if (!webFrag.exists() || webFrag.isDirectory())
258             {
259                 webFrag.close();
260                 webFrag = EmptyResource.INSTANCE;
261             }
262             
263             if (cache != null)
264             {
265                 //web-fragment.xml doesn't exist: put token in cache to signal we've seen the jar               
266                 Resource old = cache.putIfAbsent(jar, webFrag);
267                 if (old != null)
268                     webFrag = old;
269                 else
270                     if (LOG.isDebugEnabled()) LOG.debug(jar+" META-INF/web-fragment.xml cache updated");
271             }
272             
273             if (webFrag == EmptyResource.INSTANCE)
274                 return;
275         }
276 
277         Map<Resource, Resource> fragments = (Map<Resource,Resource>)context.getAttribute(METAINF_FRAGMENTS);
278         if (fragments == null)
279         {
280             fragments = new HashMap<Resource, Resource>();
281             context.setAttribute(METAINF_FRAGMENTS, fragments);
282         }
283         fragments.put(jar, webFrag);   
284         if (LOG.isDebugEnabled()) LOG.debug(webFrag+" added to context");
285     }
286     
287     
288     /**
289      * Discover META-INF/*.tld files in the given jar
290      * 
291      * @param context the context for the scan
292      * @param jar the jar resources to scan tlds for
293      * @param cache the resource cache
294      * @throws Exception if unable to scan for tlds
295      */
296     public void scanForTlds (WebAppContext context, Resource jar, ConcurrentHashMap<Resource, Collection<URL>> cache)
297     throws Exception
298     {
299         Collection<URL> tlds = null;
300         
301         if (cache != null && cache.containsKey(jar))
302         {
303             Collection<URL> tmp = cache.get(jar);
304             if (tmp.isEmpty())
305             {
306                 if (LOG.isDebugEnabled()) LOG.debug(jar+" cached as containing no tlds");
307                 return;
308             }
309             else
310             {
311                 tlds = tmp;
312                 if (LOG.isDebugEnabled()) LOG.debug(jar+" tlds found in cache ");
313             }
314         }
315         else
316         {
317             //not using caches or not in the cache so find all tlds
318             tlds = new HashSet<URL>();  
319             if (jar.isDirectory())
320             {
321                 tlds.addAll(getTlds(jar.getFile()));
322             }
323             else
324             {
325                 URI uri = jar.getURI();
326                 tlds.addAll(getTlds(uri));
327             }
328 
329             if (cache != null)
330             {  
331                 if (LOG.isDebugEnabled()) LOG.debug(jar+" tld cache updated");
332                 Collection<URL> old = (Collection<URL>)cache.putIfAbsent(jar, tlds);
333                 if (old != null)
334                     tlds = old;
335             }
336             
337             if (tlds.isEmpty())
338                 return;
339         }
340 
341         Collection<URL> metaInfTlds = (Collection<URL>)context.getAttribute(METAINF_TLDS);
342         if (metaInfTlds == null)
343         {
344             metaInfTlds = new HashSet<URL>();
345             context.setAttribute(METAINF_TLDS, metaInfTlds);
346         }
347         metaInfTlds.addAll(tlds);  
348         if (LOG.isDebugEnabled()) LOG.debug("tlds added to context");
349     }
350     
351    
352     @Override
353     public void postConfigure(WebAppContext context) throws Exception
354     {
355         context.setAttribute(METAINF_RESOURCES, null);
356 
357         context.setAttribute(METAINF_FRAGMENTS, null); 
358    
359         context.setAttribute(METAINF_TLDS, null);
360     }
361     
362     /**
363      * Find all .tld files in all subdirs of the given dir.
364      * 
365      * @param dir the directory to scan
366      * @return the list of tlds found
367      * @throws IOException if unable to scan the directory
368      */
369     public Collection<URL>  getTlds (File dir) throws IOException
370     {
371         if (dir == null || !dir.isDirectory())
372             return Collections.emptySet();
373         
374         HashSet<URL> tlds = new HashSet<URL>();
375         
376         File[] files = dir.listFiles();
377         if (files != null)
378         {
379             for (File f:files)
380             {
381                 if (f.isDirectory())
382                     tlds.addAll(getTlds(f));
383                 else
384                 {
385                     String name = f.getCanonicalPath();
386                     if (name.contains("META-INF") && name.endsWith(".tld"))
387                         tlds.add(f.toURI().toURL());
388                 }
389             }
390         }
391         return tlds;  
392     }
393     
394     /**
395      * Find all .tld files in the given jar.
396      * 
397      * @param uri the uri to jar file
398      * @return the collection of tlds as url references  
399      * @throws IOException if unable to scan the jar file
400      */
401     public Collection<URL> getTlds (URI uri) throws IOException
402     {
403         HashSet<URL> tlds = new HashSet<URL>();
404         
405         URL url = new URL("jar:"+uri+"!/");
406         JarURLConnection jarConn = (JarURLConnection) url.openConnection();
407         jarConn.setUseCaches(Resource.getDefaultUseCaches());
408         JarFile jarFile = jarConn.getJarFile();
409         Enumeration<JarEntry> entries = jarFile.entries();
410         while (entries.hasMoreElements())
411         {
412             JarEntry e = entries.nextElement();
413             String name = e.getName();
414             if (name.startsWith("META-INF") && name.endsWith(".tld"))
415             {
416                 tlds.add(new URL("jar:"+uri+"!/"+name));
417             }
418         }
419         if (!Resource.getDefaultUseCaches())
420             jarFile.close();
421         return tlds;
422     }
423 }