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 }