View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.vfs.impl;
    18
    19  import org.apache.commons.vfs.FileObject;
    20  import org.apache.commons.vfs.FileSystemException;
    21  import org.apache.commons.vfs.FileSystemManager;
    22  import org.apache.commons.vfs.FileType;
    23  import org.apache.commons.vfs.NameScope;
    24
    25  import java.io.IOException;
    26  import java.net.URL;
    27  import java.security.CodeSource;
    28  import java.security.Permission;
    29  import java.security.PermissionCollection;
    30  import java.security.Permissions;
    31  import java.security.SecureClassLoader;
    32  import java.security.cert.Certificate;
    33  import java.util.ArrayList;
    34  import java.util.Enumeration;
    35  import java.util.Iterator;
    36  import java.util.jar.Attributes;
    37  import java.util.jar.Attributes.Name;
    38
    39
    40  /***
    41   * A class loader that can load classes and resources from a search path
    42   * VFS FileObjects refering both to folders and JAR files. Any FileObject
    43   * of type {@link FileType#FILE} is asumed to be a JAR and is opened
    44   * by creating a layered file system with the "jar" scheme.
    45   * </p><p>
    46   * TODO - Test this with signed Jars and a SecurityManager.
    47   *
    48   * @author <a href="mailto:brian@mmmanager.org">Brian Olsen</a>
    49   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
    50   * @see FileSystemManager#createFileSystem
    51   */
    52  public class VFSClassLoader
    53      extends SecureClassLoader
    54  {
    55      private final ArrayList resources = new ArrayList();
    56
    57      /***
    58       * Constructors a new VFSClassLoader for the given file.
    59       *
    60       * @param file    the file to load the classes and resources from.
    61       * @param manager the FileManager to use when trying create a layered Jar file
    62       *                system.
    63       */
    64      public VFSClassLoader(final FileObject file,
    65                            final FileSystemManager manager)
    66          throws FileSystemException
    67      {
    68          this(new FileObject[]{file}, manager, null);
    69      }
    70
    71      /***
    72       * Constructors a new VFSClassLoader for the given file.
    73       *
    74       * @param file    the file to load the classes and resources from.
    75       * @param manager the FileManager to use when trying create a layered Jar file
    76       *                system.
    77       * @param parent  the parent class loader for delegation.
    78       */
    79      public VFSClassLoader(final FileObject file,
    80                            final FileSystemManager manager,
    81                            final ClassLoader parent)
    82          throws FileSystemException
    83      {
    84          this(new FileObject[]{file}, manager, parent);
    85      }
    86
    87      /***
    88       * Constructors a new VFSClassLoader for the given files.  The files will
    89       * be searched in the order specified.
    90       *
    91       * @param files   the files to load the classes and resources from.
    92       * @param manager the FileManager to use when trying create a layered Jar file
    93       *                system.
    94       */
    95      public VFSClassLoader(final FileObject[] files,
    96                            final FileSystemManager manager)
    97          throws FileSystemException
    98      {
    99          this(files, manager, null);
    100     }
    101
    102     /***
    103      * Constructors a new VFSClassLoader for the given FileObjects.
    104      * The FileObjects will be searched in the order specified.
    105      *
    106      * @param files   the FileObjects to load the classes and resources from.
    107      * @param manager the FileManager to use when trying create a layered Jar file
    108      *                system.
    109      * @param parent  the parent class loader for delegation.
    110      */
    111     public VFSClassLoader(final FileObject[] files,
    112                           final FileSystemManager manager,
    113                           final ClassLoader parent) throws FileSystemException
    114     {
    115         super(parent);
    116         addFileObjects(manager, files);
    117     }
    118
    119     /***
    120      * Appends the specified FileObjects to the list of FileObjects to search
    121      * for classes and resources.
    122      *
    123      * @param files the FileObjects to append to the search path.
    124      */
    125     private void addFileObjects(final FileSystemManager manager,
    126                                 final FileObject[] files) throws FileSystemException
    127     {
    128         for (int i = 0; i < files.length; i++)
    129         {
    130             FileObject file = files[i];
    131             if (!file.exists())
    132             {
    133                 // Does not exist - skip
    134                 continue;
    135             }
    136
    137             // TODO - use federation instead
    138             if (manager.canCreateFileSystem(file))
    139             {
    140                 // Use contents of the file
    141                 file = manager.createFileSystem(file);
    142             }
    143
    144             resources.add(file);
    145         }
    146     }
    147
    148     /***
    149      * Finds and loads the class with the specified name from the search
    150      * path.
    151      *
    152      * @throws ClassNotFoundException if the class is not found.
    153      */
    154     protected Class findClass(final String name) throws ClassNotFoundException
    155     {
    156         try
    157         {
    158             final String path = name.replace('.', '/').concat(".class");
    159             final Resource res = loadResource(path);
    160             if (res == null)
    161             {
    162                 throw new ClassNotFoundException(name);
    163             }
    164             return defineClass(name, res);
    165         }
    166         catch (final IOException ioe)
    167         {
    168             throw new ClassNotFoundException(name, ioe);
    169         }
    170     }
    171
    172     /***
    173      * Loads and verifies the class with name and located with res.
    174      */
    175     private Class defineClass(final String name, final Resource res)
    176         throws IOException
    177     {
    178         final URL url = res.getCodeSourceURL();
    179         final String pkgName = res.getPackageName();
    180         if (pkgName != null)
    181         {
    182             final Package pkg = getPackage(pkgName);
    183             if (pkg != null)
    184             {
    185                 if (pkg.isSealed())
    186                 {
    187                     if (!pkg.isSealed(url))
    188                     {
    189                         throw new FileSystemException("vfs.impl/pkg-sealed-other-url", pkgName);
    190                     }
    191                 }
    192                 else
    193                 {
    194                     if (isSealed(res))
    195                     {
    196                         throw new FileSystemException("vfs.impl/pkg-sealing-unsealed", pkgName);
    197                     }
    198                 }
    199             }
    200             else
    201             {
    202                 definePackage(pkgName, res);
    203             }
    204         }
    205
    206         final byte[] bytes = res.getBytes();
    207         final Certificate[] certs =
    208             res.getFileObject().getContent().getCertificates();
    209         final CodeSource cs = new CodeSource(url, certs);
    210         return defineClass(name, bytes, 0, bytes.length, cs);
    211     }
    212
    213     /***
    214      * Returns true if the we should seal the package where res resides.
    215      */
    216     private boolean isSealed(final Resource res)
    217         throws FileSystemException
    218     {
    219         final String sealed = res.getPackageAttribute(Attributes.Name.SEALED);
    220         return "true".equalsIgnoreCase(sealed);
    221     }
    222
    223     /***
    224      * Reads attributes for the package and defines it.
    225      */
    226     private Package definePackage(final String name,
    227                                   final Resource res)
    228         throws FileSystemException
    229     {
    230         // TODO - check for MANIFEST_ATTRIBUTES capability first
    231         final String specTitle = res.getPackageAttribute(Name.SPECIFICATION_TITLE);
    232         final String specVendor = res.getPackageAttribute(Attributes.Name.SPECIFICATION_VENDOR);
    233         final String specVersion = res.getPackageAttribute(Name.SPECIFICATION_VERSION);
    234         final String implTitle = res.getPackageAttribute(Name.IMPLEMENTATION_TITLE);
    235         final String implVendor = res.getPackageAttribute(Name.IMPLEMENTATION_VENDOR);
    236         final String implVersion = res.getPackageAttribute(Name.IMPLEMENTATION_VERSION);
    237
    238         final URL sealBase;
    239         if (isSealed(res))
    240         {
    241             sealBase = res.getCodeSourceURL();
    242         }
    243         else
    244         {
    245             sealBase = null;
    246         }
    247
    248         return definePackage(name, specTitle, specVersion, specVendor,
    249             implTitle, implVersion, implVendor, sealBase);
    250     }
    251
    252     /***
    253      * Calls super.getPermissions both for the code source and also
    254      * adds the permissions granted to the parent layers.
    255      */
    256     protected PermissionCollection getPermissions(final CodeSource cs)
    257     {
    258         try
    259         {
    260             final String url = cs.getLocation().toString();
    261             FileObject file = lookupFileObject(url);
    262             if (file == null)
    263             {
    264                 return super.getPermissions(cs);
    265             }
    266
    267             FileObject parentLayer = file.getFileSystem().getParentLayer();
    268             if (parentLayer == null)
    269             {
    270                 return super.getPermissions(cs);
    271             }
    272
    273             Permissions combi = new Permissions();
    274             PermissionCollection permCollect = super.getPermissions(cs);
    275             copyPermissions(permCollect, combi);
    276
    277             for (FileObject parent = parentLayer;
    278                  parent != null;
    279                  parent = parent.getFileSystem().getParentLayer())
    280             {
    281                 final CodeSource parentcs =
    282                     new CodeSource(parent.getURL(),
    283                         parent.getContent().getCertificates());
    284                 permCollect = super.getPermissions(parentcs);
    285                 copyPermissions(permCollect, combi);
    286             }
    287
    288             return combi;
    289         }
    290         catch (final FileSystemException fse)
    291         {
    292             throw new SecurityException(fse.getMessage());
    293         }
    294     }
    295
    296     /***
    297      * Copies the permissions from src to dest.
    298      */
    299     protected void copyPermissions(final PermissionCollection src,
    300                                    final PermissionCollection dest)
    301     {
    302         for (Enumeration elem = src.elements(); elem.hasMoreElements();)
    303         {
    304             final Permission permission = (Permission) elem.nextElement();
    305             dest.add(permission);
    306         }
    307     }
    308
    309     /***
    310      * Does a reverse lookup to find the FileObject when we only have the
    311      * URL.
    312      */
    313     private FileObject lookupFileObject(final String name)
    314     {
    315         final Iterator it = resources.iterator();
    316         while (it.hasNext())
    317         {
    318             final FileObject object = (FileObject) it.next();
    319             if (name.equals(object.getName().getURI()))
    320             {
    321                 return object;
    322             }
    323         }
    324         return null;
    325     }
    326
    327     /***
    328      * Finds the resource with the specified name from the search path.
    329      * This returns null if the resource is not found.
    330      */
    331     protected URL findResource(final String name)
    332     {
    333         try
    334         {
    335             final Resource res = loadResource(name);
    336             if (res != null)
    337             {
    338                 return res.getURL();
    339             }
    340         }
    341         catch (final Exception mue)
    342         {
    343             // Ignore
    344             // TODO - report?
    345         }
    346
    347         return null;
    348     }
    349
    350     /***
    351      * Returns an Enumeration of all the resources in the search path
    352      * with the specified name.
    353      * TODO - Implement this.
    354      */
    355     protected Enumeration findResources(final String name)
    356     {
    357         return new Enumeration()
    358         {
    359             public boolean hasMoreElements()
    360             {
    361                 return false;
    362             }
    363
    364             public Object nextElement()
    365             {
    366                 return null;
    367             }
    368         };
    369     }
    370
    371     /***
    372      * Searches through the search path of for the first class or resource
    373      * with specified name.
    374      */
    375     private Resource loadResource(final String name) throws FileSystemException
    376     {
    377         final Iterator it = resources.iterator();
    378         while (it.hasNext())
    379         {
    380             final FileObject baseFile = (FileObject) it.next();
    381             final FileObject file =
    382                 baseFile.resolveFile(name, NameScope.DESCENDENT_OR_SELF);
    383             if (file.exists())
    384             {
    385                 return new Resource(name, baseFile, file);
    386             }
    387         }
    388
    389         return null;
    390     }
    391 }