1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
134 continue;
135 }
136
137
138 if (manager.canCreateFileSystem(file))
139 {
140
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
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
344
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 }