View Javadoc

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