View Javadoc

    1   // ========================================================================
    2   // Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.webapp;
    16
    17  import java.io.File;
    18  import java.io.FileOutputStream;
    19  import java.io.IOException;
    20  import java.io.InputStream;
    21  import java.net.URL;
    22  import java.net.URLClassLoader;
    23  import java.security.CodeSource;
    24  import java.security.PermissionCollection;
    25  import java.util.StringTokenizer;
    26
    27  import org.mortbay.jetty.handler.ContextHandler;
    28  import org.mortbay.log.Log;
    29  import org.mortbay.resource.Resource;
    30  import org.mortbay.util.IO;
    31  import org.mortbay.util.LazyList;
    32  import org.mortbay.util.StringUtil;
    33
    34
    35  /* ------------------------------------------------------------ */
    36  /** ClassLoader for HttpContext.
    37   * Specializes URLClassLoader with some utility and file mapping
    38   * methods.
    39   *
    40   * This loader defaults to the 2.3 servlet spec behaviour where non
    41   * system classes are loaded from the classpath in preference to the
    42   * parent loader.  Java2 compliant loading, where the parent loader
    43   * always has priority, can be selected with the
    44   * {@link org.mortbay.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} method.
    45   *
    46   * If no parent class loader is provided, then the current thread context classloader will
    47   * be used.  If that is null then the classloader that loaded this class is used as the parent.
    48   *
    49   * @author Greg Wilkins (gregw)
    50   */
    51  public class WebAppClassLoader extends URLClassLoader
    52  {
    53      private String _name;
    54      private WebAppContext _context;
    55      private ClassLoader _parent;
    56
    57      /* ------------------------------------------------------------ */
    58      /** Constructor.
    59       */
    60      public WebAppClassLoader(WebAppContext context)
    61          throws IOException
    62      {
    63          this(null,context);
    64      }
    65
    66      /* ------------------------------------------------------------ */
    67      /** Constructor.
    68       */
    69      public WebAppClassLoader(ClassLoader parent, WebAppContext context)
    70          throws IOException
    71      {
    72          super(new URL[]{},parent!=null?parent
    73                  :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
    74                          :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
    75                                  :ClassLoader.getSystemClassLoader())));
    76          _parent=getParent();
    77          _context=context;
    78          if (_parent==null)
    79              throw new IllegalArgumentException("no parent classloader!");
    80
    81          if (context.getExtraClasspath()!=null)
    82              addClassPath(context.getExtraClasspath());
    83      }
    84
    85      /* ------------------------------------------------------------ */
    86      /**
    87       * @return the name of the classloader
    88       */
    89      public String getName()
    90      {
    91          return _name;
    92      }
    93
    94      /* ------------------------------------------------------------ */
    95      /**
    96       * @param name the name of the classloader
    97       */
    98      public void setName(String name)
    99      {
    100         _name=name;
    101     }
    102
    103
    104     /* ------------------------------------------------------------ */
    105     public ContextHandler getContext()
    106     {
    107         return _context;
    108     }
    109
    110     /* ------------------------------------------------------------ */
    111     /**
    112      * @param classPath Comma or semicolon separated path of filenames or URLs
    113      * pointing to directories or jar files. Directories should end
    114      * with '/'.
    115      */
    116     public void addClassPath(String classPath)
    117     	throws IOException
    118     {
    119         if (classPath == null)
    120             return;
    121
    122         StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
    123         while (tokenizer.hasMoreTokens())
    124         {
    125             Resource resource= Resource.newResource(tokenizer.nextToken());
    126             if (Log.isDebugEnabled())
    127                 Log.debug("Path resource=" + resource);
    128
    129             // Resolve file path if possible
    130             File file= resource.getFile();
    131             if (file != null)
    132             {
    133                 URL url= resource.getURL();
    134                 addURL(url);
    135             }
    136             else
    137             {
    138                 // Add resource or expand jar/
    139                 if (!resource.isDirectory() && file == null)
    140                 {
    141                     InputStream in= resource.getInputStream();
    142                     File tmp_dir=_context.getTempDirectory();
    143                     if (tmp_dir==null)
    144                     {
    145                         tmp_dir = File.createTempFile("jetty.cl.lib",null);
    146                         tmp_dir.mkdir();
    147                         tmp_dir.deleteOnExit();
    148                     }
    149                     File lib= new File(tmp_dir, "lib");
    150                     if (!lib.exists())
    151                     {
    152                         lib.mkdir();
    153                         lib.deleteOnExit();
    154                     }
    155                     File jar= File.createTempFile("Jetty-", ".jar", lib);
    156
    157                     jar.deleteOnExit();
    158                     if (Log.isDebugEnabled())
    159                         Log.debug("Extract " + resource + " to " + jar);
    160                     FileOutputStream out = null;
    161                     try
    162                     {
    163                         out= new FileOutputStream(jar);
    164                         IO.copy(in, out);
    165                     }
    166                     finally
    167                     {
    168                         IO.close(out);
    169                     }
    170
    171                     URL url= jar.toURL();
    172                     addURL(url);
    173                 }
    174                 else
    175                 {
    176                     URL url= resource.getURL();
    177                     addURL(url);
    178                 }
    179             }
    180         }
    181     }
    182
    183
    184
    185     /* ------------------------------------------------------------ */
    186     /** Add elements to the class path for the context from the jar and zip files found
    187      *  in the specified resource.
    188      * @param lib the resource that contains the jar and/or zip files.
    189      * @param append true if the classpath entries are to be appended to any
    190      * existing classpath, or false if they replace the existing classpath.
    191      * @see #setClassPath(String)
    192      */
    193     public void addJars(Resource lib)
    194     {
    195         if (lib.exists() && lib.isDirectory())
    196         {
    197             String[] files=lib.list();
    198             for (int f=0;files!=null && f<files.length;f++)
    199             {
    200                 try {
    201                     Resource fn=lib.addPath(files[f]);
    202                     String fnlc=fn.getName().toLowerCase();
    203                     if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip"))
    204                     {
    205                     	String jar=fn.toString();
    206                     	jar=StringUtil.replace(jar, ",", "%2C");
    207                     	jar=StringUtil.replace(jar, ";", "%3B");
    208                         addClassPath(jar);
    209                     }
    210                 }
    211                 catch (Exception ex)
    212                 {
    213                     Log.warn(Log.EXCEPTION,ex);
    214                 }
    215             }
    216         }
    217     }
    218     /* ------------------------------------------------------------ */
    219     public void destroy()
    220     {
    221         this._parent=null;
    222     }
    223
    224
    225     /* ------------------------------------------------------------ */
    226     public PermissionCollection getPermissions(CodeSource cs)
    227     {
    228         // TODO check CodeSource
    229         PermissionCollection permissions=_context.getPermissions();
    230         PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
    231         return pc;
    232     }
    233
    234     /* ------------------------------------------------------------ */
    235     public synchronized URL getResource(String name)
    236     {
    237         URL url= null;
    238         boolean tried_parent= false;
    239         if (_context.isParentLoaderPriority() || isSystemPath(name))
    240         {
    241             tried_parent= true;
    242
    243             if (_parent!=null)
    244                 url= _parent.getResource(name);
    245         }
    246
    247         if (url == null)
    248         {
    249             url= this.findResource(name);
    250
    251             if (url == null && name.startsWith("/"))
    252             {
    253                 if (Log.isDebugEnabled())
    254                     Log.debug("HACK leading / off " + name);
    255                 url= this.findResource(name.substring(1));
    256             }
    257         }
    258
    259         if (url == null && !tried_parent)
    260         {
    261             if (_parent!=null)
    262                 url= _parent.getResource(name);
    263         }
    264
    265         if (url != null)
    266             if (Log.isDebugEnabled())
    267                 Log.debug("getResource("+name+")=" + url);
    268
    269         return url;
    270     }
    271
    272     /* ------------------------------------------------------------ */
    273     public boolean isServerPath(String name)
    274     {
    275         name=name.replace('/','.');
    276         while(name.startsWith("."))
    277             name=name.substring(1);
    278
    279         String[] server_classes = _context.getServerClasses();
    280         if (server_classes!=null)
    281         {
    282             for (int i=0;i<server_classes.length;i++)
    283             {
    284                 boolean result=true;
    285                 String c=server_classes[i];
    286                 if (c.startsWith("-"))
    287                 {
    288                     c=c.substring(1); // TODO cache
    289                     result=false;
    290                 }
    291
    292                 if (c.endsWith("."))
    293                 {
    294                     if (name.startsWith(c))
    295                         return result;
    296                 }
    297                 else if (name.equals(c))
    298                     return result;
    299             }
    300         }
    301         return false;
    302     }
    303
    304     /* ------------------------------------------------------------ */
    305     public boolean isSystemPath(String name)
    306     {
    307         name=name.replace('/','.');
    308         while(name.startsWith("."))
    309             name=name.substring(1);
    310         String[] system_classes = _context.getSystemClasses();
    311         if (system_classes!=null)
    312         {
    313             for (int i=0;i<system_classes.length;i++)
    314             {
    315                 boolean result=true;
    316                 String c=system_classes[i];
    317
    318                 if (c.startsWith("-"))
    319                 {
    320                     c=c.substring(1); // TODO cache
    321                     result=false;
    322                 }
    323
    324                 if (c.endsWith("."))
    325                 {
    326                     if (name.startsWith(c))
    327                         return result;
    328                 }
    329                 else if (name.equals(c))
    330                     return result;
    331             }
    332         }
    333
    334         return false;
    335
    336     }
    337
    338     /* ------------------------------------------------------------ */
    339     public synchronized Class loadClass(String name) throws ClassNotFoundException
    340     {
    341         return loadClass(name, false);
    342     }
    343
    344     /* ------------------------------------------------------------ */
    345     protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException
    346     {
    347         Class c= findLoadedClass(name);
    348         ClassNotFoundException ex= null;
    349         boolean tried_parent= false;
    350
    351         if (c == null && _parent!=null && (_context.isParentLoaderPriority() || isSystemPath(name)) )
    352         {
    353             tried_parent= true;
    354             try
    355             {
    356                 c= _parent.loadClass(name);
    357                 if (Log.isDebugEnabled())
    358                     Log.debug("loaded " + c);
    359             }
    360             catch (ClassNotFoundException e)
    361             {
    362                 ex= e;
    363             }
    364         }
    365
    366         if (c == null)
    367         {
    368             try
    369             {
    370                 c= this.findClass(name);
    371             }
    372             catch (ClassNotFoundException e)
    373             {
    374                 ex= e;
    375             }
    376         }
    377
    378         if (c == null && _parent!=null && !tried_parent && !isServerPath(name) )
    379             c= _parent.loadClass(name);
    380
    381         if (c == null)
    382             throw ex;
    383
    384         if (resolve)
    385             resolveClass(c);
    386
    387         if (Log.isDebugEnabled())
    388             Log.debug("loaded " + c+ " from "+c.getClassLoader());
    389
    390         return c;
    391     }
    392
    393     /* ------------------------------------------------------------ */
    394     public String toString()
    395     {
    396         if (Log.isDebugEnabled())
    397             return "ContextLoader@" + _name + "(" + LazyList.array2List(getURLs()) + ") / " + _parent;
    398         return "ContextLoader@" + _name;
    399     }
    400
    401 }