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