View Javadoc

1   package org.eclipse.jetty.webapp;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.URI;
6   import java.net.URISyntaxException;
7   import java.net.URL;
8   import java.net.URLClassLoader;
9   import java.util.ArrayList;
10  import java.util.List;
11  import java.util.regex.Pattern;
12  
13  import org.eclipse.jetty.server.Connector;
14  import org.eclipse.jetty.server.Server;
15  import org.eclipse.jetty.util.IO;
16  import org.eclipse.jetty.util.PatternMatcher;
17  import org.eclipse.jetty.util.URIUtil;
18  import org.eclipse.jetty.util.log.Log;
19  import org.eclipse.jetty.util.log.Logger;
20  import org.eclipse.jetty.util.resource.JarResource;
21  import org.eclipse.jetty.util.resource.Resource;
22  import org.eclipse.jetty.util.resource.ResourceCollection;
23  
24  public class WebInfConfiguration extends AbstractConfiguration
25  {
26      private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
27  
28      public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
29      public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
30      public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";
31      
32      /**
33       * If set, to a list of URLs, these resources are added to the context
34       * resource base as a resource collection. 
35       */
36      public static final String RESOURCE_URLS = "org.eclipse.jetty.resources";
37      
38      protected Resource _preUnpackBaseResource;
39      
40      @Override
41      public void preConfigure(final WebAppContext context) throws Exception
42      {
43          // Look for a work directory
44          File work = findWorkDirectory(context);
45          if (work != null)
46              makeTempDirectory(work, context, false);
47          
48          //Make a temp directory for the webapp if one is not already set
49          resolveTempDirectory(context);
50          
51          //Extract webapp if necessary
52          unpack (context);
53  
54          
55          //Apply an initial ordering to the jars which governs which will be scanned for META-INF
56          //info and annotations. The ordering is based on inclusion patterns.       
57          String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
58          Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
59          tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
60          Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
61  
62          //Apply ordering to container jars - if no pattern is specified, we won't
63          //match any of the container jars
64          PatternMatcher containerJarNameMatcher = new PatternMatcher ()
65          {
66              public void matched(URI uri) throws Exception
67              {
68                  context.getMetaData().addContainerJar(Resource.newResource(uri));
69              }      
70          };
71          ClassLoader loader = context.getClassLoader();
72          while (loader != null && (loader instanceof URLClassLoader))
73          {
74              URL[] urls = ((URLClassLoader)loader).getURLs();
75              if (urls != null)
76              {
77                  URI[] containerUris = new URI[urls.length];
78                  int i=0;
79                  for (URL u : urls)
80                  {
81                      try 
82                      {
83                          containerUris[i] = u.toURI();
84                      }
85                      catch (URISyntaxException e)
86                      {
87                          containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
88                      }  
89                      i++;
90                  }
91                  containerJarNameMatcher.match(containerPattern, containerUris, false);
92              }
93              loader = loader.getParent();
94          }
95          
96          //Apply ordering to WEB-INF/lib jars
97          PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
98          {
99              @Override
100             public void matched(URI uri) throws Exception
101             {
102                 context.getMetaData().addWebInfJar(Resource.newResource(uri));
103             }      
104         };
105         List<Resource> jars = findJars(context);
106        
107         //Convert to uris for matching
108         URI[] uris = null;
109         if (jars != null)
110         {
111             uris = new URI[jars.size()];
112             int i=0;
113             for (Resource r: jars)
114             {
115                 uris[i++] = r.getURI();
116             }
117         }
118         webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
119     }
120     
121 
122     @Override
123     public void configure(WebAppContext context) throws Exception
124     {
125         //cannot configure if the context is already started
126         if (context.isStarted())
127         {
128             if (LOG.isDebugEnabled())
129                 LOG.debug("Cannot configure webapp "+context+" after it is started");
130             return;
131         }
132 
133         Resource web_inf = context.getWebInf();
134 
135         // Add WEB-INF classes and lib classpaths
136         if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
137         {
138             // Look for classes directory
139             Resource classes= web_inf.addPath("classes/");
140             if (classes.exists())
141                 ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
142 
143             // Look for jars
144             Resource lib= web_inf.addPath("lib/");
145             if (lib.exists() || lib.isDirectory())
146                 ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
147         }
148         
149         // Look for extra resource
150         @SuppressWarnings("unchecked")
151         List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
152         if (resources!=null)
153         {
154             Resource[] collection=new Resource[resources.size()+1];
155             int i=0;
156             collection[i++]=context.getBaseResource();
157             for (Resource resource : resources)
158                 collection[i++]=resource;
159             context.setBaseResource(new ResourceCollection(collection));
160         }
161     }
162 
163     @Override
164     public void deconfigure(WebAppContext context) throws Exception
165     {
166         // delete temp directory if we had to create it or if it isn't called work
167         Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
168         
169         if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
170         {
171             IO.delete(context.getTempDirectory());
172             context.setTempDirectory(null);
173             
174             //clear out the context attributes for the tmp dir only if we had to
175             //create the tmp dir
176             context.setAttribute(TEMPDIR_CONFIGURED, null);
177             context.setAttribute(WebAppContext.TEMPDIR, null);
178         }
179 
180         
181         //reset the base resource back to what it was before we did any unpacking of resources
182         context.setBaseResource(_preUnpackBaseResource);
183     }
184     
185     /* ------------------------------------------------------------ */
186     /**
187      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
188      */
189     @Override
190     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
191     {
192         File tmpDir=File.createTempFile(WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context),"",template.getTempDirectory().getParentFile());
193         if (tmpDir.exists())
194         {
195             IO.delete(tmpDir);
196         }
197         tmpDir.mkdir();
198         tmpDir.deleteOnExit();
199         context.setTempDirectory(tmpDir);
200     }
201 
202 
203     /* ------------------------------------------------------------ */
204     /**
205      * Get a temporary directory in which to unpack the war etc etc.
206      * The algorithm for determining this is to check these alternatives
207      * in the order shown:
208      * 
209      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
210      * <ol>
211      * <li>
212      * Iff an explicit directory is set for this webapp, use it. Do NOT set
213      * delete on exit.
214      * </li>
215      * <li>
216      * Iff javax.servlet.context.tempdir context attribute is set for
217      * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
218      * </li>
219      * </ol>
220      * 
221      * <p>B. Create a directory based on global settings. The new directory 
222      * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
223      * Work out where to create this directory:
224      * <ol>
225      * <li>
226      * Iff $(jetty.home)/work exists create the directory there. Do NOT
227      * set delete on exit. Do NOT delete contents if dir already exists.
228      * </li>
229      * <li>
230      * Iff WEB-INF/work exists create the directory there. Do NOT set
231      * delete on exit. Do NOT delete contents if dir already exists.
232      * </li>
233      * <li>
234      * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
235      * contents if dir already exists.
236      * </li>
237      * </ol>
238      */
239     public void resolveTempDirectory (WebAppContext context)
240     {
241         //If a tmp directory is already set, we're done
242         File tmpDir = context.getTempDirectory();
243         if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite())
244         {
245             context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE);
246             return; // Already have a suitable tmp dir configured
247         }
248         
249 
250         // No temp directory configured, try to establish one.
251         // First we check the context specific, javax.servlet specified, temp directory attribute
252         File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
253         if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite())
254         {
255             // Use as tmpDir
256             tmpDir = servletTmpDir;
257             // Ensure Attribute has File object
258             context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
259             // Set as TempDir in context.
260             context.setTempDirectory(tmpDir);
261             return;
262         }
263 
264         try
265         {
266             // Put the tmp dir in the work directory if we had one
267             File work =  new File(System.getProperty("jetty.home"),"work");
268             if (work.exists() && work.canWrite() && work.isDirectory())
269             {
270                 makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists
271             }
272             else
273             {
274                 File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
275                 if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
276                 {
277                     // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in
278                     makeTempDirectory(baseTemp,context,false);
279                 }
280                 else
281                 {
282                     makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists
283                 }
284             }
285         }
286         catch(Exception e)
287         {
288             tmpDir=null;
289             LOG.ignore(e);
290         }
291 
292         //Third ... Something went wrong trying to make the tmp directory, just make
293         //a jvm managed tmp directory
294         if (context.getTempDirectory() == null)
295         {
296             try
297             {
298                 // Last resort
299                 tmpDir=File.createTempFile("JettyContext","");
300                 if (tmpDir.exists())
301                     IO.delete(tmpDir);
302                 tmpDir.mkdir();
303                 tmpDir.deleteOnExit();
304                 context.setTempDirectory(tmpDir);
305             }
306             catch(IOException e)
307             {
308                 tmpDir = null;
309                 throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e);
310             }
311         }
312     }
313     
314     /**
315      * Given an Object, return File reference for object.
316      * Typically used to convert anonymous Object from getAttribute() calls to a File object.
317      * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null)
318      * @return the File object, null if null, or null if not a File or String
319      */
320     private File asFile(Object fileattr)
321     {
322         if (fileattr == null)
323         {
324             return null;
325         }
326         if (fileattr instanceof File)
327         {
328             return (File)fileattr;
329         }
330         if (fileattr instanceof String)
331         {
332             return new File((String)fileattr);
333         }
334         return null;
335     }
336 
337 
338 
339     public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting)
340     throws IOException
341     {
342         if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory())
343         {
344             String temp = getCanonicalNameForWebAppTmpDir(context);                    
345             File tmpDir = new File(parent,temp);
346 
347             if (deleteExisting && tmpDir.exists())
348             {
349                 if (!IO.delete(tmpDir))
350                 {
351                     if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir);
352                 }
353             
354                 //If we can't delete the existing tmp dir, create a new one
355                 if (tmpDir.exists())
356                 {
357                     String old=tmpDir.toString();
358                     tmpDir=File.createTempFile(temp+"_","");
359                     if (tmpDir.exists())
360                         IO.delete(tmpDir);
361                     LOG.warn("Can't reuse "+old+", using "+tmpDir);
362                 } 
363             }
364             
365             if (!tmpDir.exists())
366                 tmpDir.mkdir();
367 
368             //If the parent is not a work directory
369             if (!isTempWorkDirectory(tmpDir))
370             {
371                 tmpDir.deleteOnExit();
372             }
373 
374             if(LOG.isDebugEnabled())
375                 LOG.debug("Set temp dir "+tmpDir);
376             context.setTempDirectory(tmpDir);
377         }
378     }
379     
380     
381     public void unpack (WebAppContext context) throws IOException
382     {
383         Resource web_app = context.getBaseResource();
384         _preUnpackBaseResource = context.getBaseResource();
385         
386         if (web_app == null)
387         {
388             String war = context.getWar();
389             if (war!=null && war.length()>0)
390                 web_app = context.newResource(war);
391             else
392                 web_app=context.getBaseResource();
393 
394             // Accept aliases for WAR files
395             if (web_app.getAlias() != null)
396             {
397                 LOG.debug(web_app + " anti-aliased to " + web_app.getAlias());
398                 web_app = context.newResource(web_app.getAlias());
399             }
400 
401             if (LOG.isDebugEnabled())
402                 LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()+" file="+(web_app.getFile()));
403             // Is the WAR usable directly?
404             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
405             {
406                 // No - then lets see if it can be turned into a jar URL.
407                 Resource jarWebApp = JarResource.newJarResource(web_app);
408                 if (jarWebApp.exists() && jarWebApp.isDirectory())
409                     web_app= jarWebApp;
410             }
411 
412             // If we should extract or the URL is still not usable
413             if (web_app.exists()  && (
414                     (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) ||
415                     (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) ||
416                     (context.isExtractWAR() && web_app.getFile() == null) || 
417                     !web_app.isDirectory())
418                             )
419             {
420                 // Look for sibling directory.
421                 File extractedWebAppDir = null;
422 
423                 if (war!=null)
424                 {
425                     // look for a sibling like "foo/" to a "foo.war"
426                     File warfile=Resource.newResource(war).getFile();
427                     if (warfile!=null && warfile.getName().toLowerCase().endsWith(".war"))
428                     {
429                         File sibling = new File(warfile.getParent(),warfile.getName().substring(0,warfile.getName().length()-4));
430                         if (sibling.exists() && sibling.isDirectory() && sibling.canWrite())
431                             extractedWebAppDir=sibling;
432                     }
433                 }
434                 
435                 if (extractedWebAppDir==null)
436                     // Then extract it if necessary to the temporary location
437                     extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
438 
439                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
440                 {
441                     // Copy directory
442                     LOG.info("Copy " + web_app + " to " + extractedWebAppDir);
443                     web_app.copyTo(extractedWebAppDir);
444                 }
445                 else
446                 {
447                     //Use a sentinel file that will exist only whilst the extraction is taking place.
448                     //This will help us detect interrupted extractions.
449                     File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
450                    
451                     if (!extractedWebAppDir.exists())
452                     {
453                         //it hasn't been extracted before so extract it
454                         extractionLock.createNewFile();  
455                         extractedWebAppDir.mkdir();
456                         LOG.info("Extract " + web_app + " to " + extractedWebAppDir);                                     
457                         Resource jar_web_app = JarResource.newJarResource(web_app);
458                         jar_web_app.copyTo(extractedWebAppDir);
459                         extractionLock.delete();
460                     }
461                     else
462                     {
463                         //only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
464                         if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
465                         {
466                             extractionLock.createNewFile();
467                             IO.delete(extractedWebAppDir);
468                             extractedWebAppDir.mkdir();
469                             LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
470                             Resource jar_web_app = JarResource.newJarResource(web_app);
471                             jar_web_app.copyTo(extractedWebAppDir);
472                             extractionLock.delete();
473                         }
474                     }
475                 } 
476                 web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath());
477             }
478 
479             // Now do we have something usable?
480             if (!web_app.exists() || !web_app.isDirectory())
481             {
482                 LOG.warn("Web application not found " + war);
483                 throw new java.io.FileNotFoundException(war);
484             }
485         
486             context.setBaseResource(web_app);
487             
488             if (LOG.isDebugEnabled())
489                 LOG.debug("webapp=" + web_app);
490         }
491         
492 
493         // Do we need to extract WEB-INF/lib?
494         if (context.isCopyWebInf() && !context.isCopyWebDir())
495         {
496             Resource web_inf= web_app.addPath("WEB-INF/");
497 
498             File extractedWebInfDir= new File(context.getTempDirectory(), "webinf");
499             if (extractedWebInfDir.exists())
500                 IO.delete(extractedWebInfDir);
501             extractedWebInfDir.mkdir();
502             Resource web_inf_lib = web_inf.addPath("lib/");
503             File webInfDir=new File(extractedWebInfDir,"WEB-INF");
504             webInfDir.mkdir();
505 
506             if (web_inf_lib.exists())
507             {
508                 File webInfLibDir = new File(webInfDir, "lib");
509                 if (webInfLibDir.exists())
510                     IO.delete(webInfLibDir);
511                 webInfLibDir.mkdir();
512 
513                 LOG.info("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
514                 web_inf_lib.copyTo(webInfLibDir);
515             }
516 
517             Resource web_inf_classes = web_inf.addPath("classes/");
518             if (web_inf_classes.exists())
519             {
520                 File webInfClassesDir = new File(webInfDir, "classes");
521                 if (webInfClassesDir.exists())
522                     IO.delete(webInfClassesDir);
523                 webInfClassesDir.mkdir();
524                 LOG.info("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
525                 web_inf_classes.copyTo(webInfClassesDir);
526             }
527 
528             web_inf=Resource.newResource(extractedWebInfDir.getCanonicalPath());
529 
530             ResourceCollection rc = new ResourceCollection(web_inf,web_app);
531 
532             if (LOG.isDebugEnabled())
533                 LOG.debug("context.resourcebase = "+rc);
534 
535             context.setBaseResource(rc);   
536         }
537     }
538     
539     
540     public File findWorkDirectory (WebAppContext context) throws IOException
541     {
542         if (context.getBaseResource() != null)
543         {
544             Resource web_inf = context.getWebInf();
545             if (web_inf !=null && web_inf.exists())
546             {
547                return new File(web_inf.getFile(),"work");
548             }
549         }
550         return null;
551     }
552     
553     
554     /**
555      * Check if the tmpDir itself is called "work", or if the tmpDir
556      * is in a directory called "work".
557      * @return true if File is a temporary or work directory
558      */
559     public boolean isTempWorkDirectory (File tmpDir)
560     {
561         if (tmpDir == null)
562             return false;
563         if (tmpDir.getName().equalsIgnoreCase("work"))
564             return true;
565         File t = tmpDir.getParentFile();
566         if (t == null)
567             return false;
568         return (t.getName().equalsIgnoreCase("work"));
569     }
570     
571     
572     /**
573      * Create a canonical name for a webapp temp directory.
574      * The form of the name is:
575      *  <code>"Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string</code>
576      *  
577      *  host and port uniquely identify the server
578      *  context and virtual host uniquely identify the webapp
579      * @return the canonical name for the webapp temp directory
580      */
581     public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
582     {
583         StringBuffer canonicalName = new StringBuffer();
584         canonicalName.append("jetty-");
585        
586         //get the host and the port from the first connector 
587         Server server=context.getServer();
588         if (server!=null)
589         {
590             Connector[] connectors = context.getServer().getConnectors();
591 
592             if (connectors.length>0)
593             {
594                 //Get the host
595                 String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
596                 if (host == null)
597                     host = "0.0.0.0";
598                 canonicalName.append(host);
599                 
600                 //Get the port
601                 canonicalName.append("-");
602                 //try getting the real port being listened on
603                 int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
604                 //if not available (eg no connectors or connector not started), 
605                 //try getting one that was configured.
606                 if (port < 0)
607                     port = connectors[0].getPort();
608                 canonicalName.append(port);
609                 canonicalName.append("-");
610             }
611         }
612 
613        
614         //Resource  base
615         try
616         {
617             Resource resource = context.getBaseResource();
618             if (resource == null)
619             {
620                 if (context.getWar()==null || context.getWar().length()==0)
621                     resource=context.newResource(context.getResourceBase());
622                 
623                 // Set dir or WAR
624                 resource = context.newResource(context.getWar());
625             }
626                 
627             String tmp = URIUtil.decodePath(resource.getURL().getPath());
628             if (tmp.endsWith("/"))
629                 tmp = tmp.substring(0, tmp.length()-1);
630             if (tmp.endsWith("!"))
631                 tmp = tmp.substring(0, tmp.length() -1);
632             //get just the last part which is the filename
633             int i = tmp.lastIndexOf("/");
634             canonicalName.append(tmp.substring(i+1, tmp.length()));
635             canonicalName.append("-");
636         }
637         catch (Exception e)
638         {
639             LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
640         }
641             
642         //Context name
643         String contextPath = context.getContextPath();
644         contextPath=contextPath.replace('/','_');
645         contextPath=contextPath.replace('\\','_');
646         canonicalName.append(contextPath);
647         
648         //Virtual host (if there is one)
649         canonicalName.append("-");
650         String[] vhosts = context.getVirtualHosts();
651         if (vhosts == null || vhosts.length <= 0)
652             canonicalName.append("any");
653         else
654             canonicalName.append(vhosts[0]);
655         
656         // sanitize
657         for (int i=0;i<canonicalName.length();i++)
658         {
659             char c=canonicalName.charAt(i);
660             if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0)
661                 canonicalName.setCharAt(i,'.');
662         }        
663 
664         canonicalName.append("-");
665         return canonicalName.toString();
666     }
667     
668     /**
669      * Look for jars in WEB-INF/lib
670      * @param context
671      * @return the list of jar resources found within context 
672      * @throws Exception
673      */
674     protected List<Resource> findJars (WebAppContext context) 
675     throws Exception
676     {
677         List<Resource> jarResources = new ArrayList<Resource>();
678         
679         Resource web_inf = context.getWebInf();
680         if (web_inf==null || !web_inf.exists())
681             return null;
682         
683         Resource web_inf_lib = web_inf.addPath("/lib");
684        
685         
686         if (web_inf_lib.exists() && web_inf_lib.isDirectory())
687         {
688             String[] files=web_inf_lib.list();
689             for (int f=0;files!=null && f<files.length;f++)
690             {
691                 try 
692                 {
693                     Resource file = web_inf_lib.addPath(files[f]);
694                     String fnlc = file.getName().toLowerCase();
695                     int dot = fnlc.lastIndexOf('.');
696                     String extension = (dot < 0 ? null : fnlc.substring(dot));
697                     if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
698                     {
699                         jarResources.add(file);
700                     }
701                 }
702                 catch (Exception ex)
703                 {
704                     LOG.warn(Log.EXCEPTION,ex);
705                 }
706             }
707         }
708         return jarResources;
709     }
710 }