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