001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.vfs.impl;
018:
019: import org.apache.commons.vfs.FileObject;
020: import org.apache.commons.vfs.FileSystemException;
021: import org.apache.commons.vfs.FileSystemManager;
022: import org.apache.commons.vfs.FileType;
023: import org.apache.commons.vfs.NameScope;
024:
025: import java.io.IOException;
026: import java.net.URL;
027: import java.security.CodeSource;
028: import java.security.Permission;
029: import java.security.PermissionCollection;
030: import java.security.Permissions;
031: import java.security.SecureClassLoader;
032: import java.security.cert.Certificate;
033: import java.util.ArrayList;
034: import java.util.Enumeration;
035: import java.util.Iterator;
036: import java.util.jar.Attributes;
037: import java.util.jar.Attributes.Name;
038:
039: /**
040: * A class loader that can load classes and resources from a search path
041: * VFS FileObjects refering both to folders and JAR files. Any FileObject
042: * of type {@link FileType#FILE} is asumed to be a JAR and is opened
043: * by creating a layered file system with the "jar" scheme.
044: * </p><p>
045: * TODO - Test this with signed Jars and a SecurityManager.
046: *
047: * @author <a href="mailto:brian@mmmanager.org">Brian Olsen</a>
048: * @version $Revision: 520101 $ $Date: 2007-03-19 13:59:05 -0700 (Mon, 19 Mar 2007) $
049: * @see FileSystemManager#createFileSystem
050: */
051: public class VFSClassLoader extends SecureClassLoader {
052: private final ArrayList resources = new ArrayList();
053:
054: /**
055: * Constructors a new VFSClassLoader for the given file.
056: *
057: * @param file the file to load the classes and resources from.
058: * @param manager the FileManager to use when trying create a layered Jar file
059: * system.
060: */
061: public VFSClassLoader(final FileObject file,
062: final FileSystemManager manager) throws FileSystemException {
063: this (new FileObject[] { file }, manager, null);
064: }
065:
066: /**
067: * Constructors a new VFSClassLoader for the given file.
068: *
069: * @param file the file to load the classes and resources from.
070: * @param manager the FileManager to use when trying create a layered Jar file
071: * system.
072: * @param parent the parent class loader for delegation.
073: */
074: public VFSClassLoader(final FileObject file,
075: final FileSystemManager manager, final ClassLoader parent)
076: throws FileSystemException {
077: this (new FileObject[] { file }, manager, parent);
078: }
079:
080: /**
081: * Constructors a new VFSClassLoader for the given files. The files will
082: * be searched in the order specified.
083: *
084: * @param files the files to load the classes and resources from.
085: * @param manager the FileManager to use when trying create a layered Jar file
086: * system.
087: */
088: public VFSClassLoader(final FileObject[] files,
089: final FileSystemManager manager) throws FileSystemException {
090: this (files, manager, null);
091: }
092:
093: /**
094: * Constructors a new VFSClassLoader for the given FileObjects.
095: * The FileObjects will be searched in the order specified.
096: *
097: * @param files the FileObjects to load the classes and resources from.
098: * @param manager the FileManager to use when trying create a layered Jar file
099: * system.
100: * @param parent the parent class loader for delegation.
101: */
102: public VFSClassLoader(final FileObject[] files,
103: final FileSystemManager manager, final ClassLoader parent)
104: throws FileSystemException {
105: super (parent);
106: addFileObjects(manager, files);
107: }
108:
109: /**
110: * provide access to the file objects this class loader represents
111: */
112: public FileObject[] getFileObjects() {
113: return (FileObject[]) resources
114: .toArray(new FileObject[resources.size()]);
115: }
116:
117: /**
118: * Appends the specified FileObjects to the list of FileObjects to search
119: * for classes and resources.
120: *
121: * @param files the FileObjects to append to the search path.
122: */
123: private void addFileObjects(final FileSystemManager manager,
124: final FileObject[] files) throws FileSystemException {
125: for (int i = 0; i < files.length; i++) {
126: FileObject file = files[i];
127: if (!file.exists()) {
128: // Does not exist - skip
129: continue;
130: }
131:
132: // TODO - use federation instead
133: if (manager.canCreateFileSystem(file)) {
134: // Use contents of the file
135: file = manager.createFileSystem(file);
136: }
137:
138: resources.add(file);
139: }
140: }
141:
142: /**
143: * Finds and loads the class with the specified name from the search
144: * path.
145: *
146: * @throws ClassNotFoundException if the class is not found.
147: */
148: protected Class findClass(final String name)
149: throws ClassNotFoundException {
150: try {
151: final String path = name.replace('.', '/').concat(".class");
152: final Resource res = loadResource(path);
153: if (res == null) {
154: throw new ClassNotFoundException(name);
155: }
156: return defineClass(name, res);
157: } catch (final IOException ioe) {
158: throw new ClassNotFoundException(name, ioe);
159: }
160: }
161:
162: /**
163: * Loads and verifies the class with name and located with res.
164: */
165: private Class defineClass(final String name, final Resource res)
166: throws IOException {
167: final URL url = res.getCodeSourceURL();
168: final String pkgName = res.getPackageName();
169: if (pkgName != null) {
170: final Package pkg = getPackage(pkgName);
171: if (pkg != null) {
172: if (pkg.isSealed()) {
173: if (!pkg.isSealed(url)) {
174: throw new FileSystemException(
175: "vfs.impl/pkg-sealed-other-url",
176: pkgName);
177: }
178: } else {
179: if (isSealed(res)) {
180: throw new FileSystemException(
181: "vfs.impl/pkg-sealing-unsealed",
182: pkgName);
183: }
184: }
185: } else {
186: definePackage(pkgName, res);
187: }
188: }
189:
190: final byte[] bytes = res.getBytes();
191: final Certificate[] certs = res.getFileObject().getContent()
192: .getCertificates();
193: final CodeSource cs = new CodeSource(url, certs);
194: return defineClass(name, bytes, 0, bytes.length, cs);
195: }
196:
197: /**
198: * Returns true if the we should seal the package where res resides.
199: */
200: private boolean isSealed(final Resource res)
201: throws FileSystemException {
202: final String sealed = res
203: .getPackageAttribute(Attributes.Name.SEALED);
204: return "true".equalsIgnoreCase(sealed);
205: }
206:
207: /**
208: * Reads attributes for the package and defines it.
209: */
210: private Package definePackage(final String name, final Resource res)
211: throws FileSystemException {
212: // TODO - check for MANIFEST_ATTRIBUTES capability first
213: final String specTitle = res
214: .getPackageAttribute(Name.SPECIFICATION_TITLE);
215: final String specVendor = res
216: .getPackageAttribute(Attributes.Name.SPECIFICATION_VENDOR);
217: final String specVersion = res
218: .getPackageAttribute(Name.SPECIFICATION_VERSION);
219: final String implTitle = res
220: .getPackageAttribute(Name.IMPLEMENTATION_TITLE);
221: final String implVendor = res
222: .getPackageAttribute(Name.IMPLEMENTATION_VENDOR);
223: final String implVersion = res
224: .getPackageAttribute(Name.IMPLEMENTATION_VERSION);
225:
226: final URL sealBase;
227: if (isSealed(res)) {
228: sealBase = res.getCodeSourceURL();
229: } else {
230: sealBase = null;
231: }
232:
233: return definePackage(name, specTitle, specVersion, specVendor,
234: implTitle, implVersion, implVendor, sealBase);
235: }
236:
237: /**
238: * Calls super.getPermissions both for the code source and also
239: * adds the permissions granted to the parent layers.
240: */
241: protected PermissionCollection getPermissions(final CodeSource cs) {
242: try {
243: final String url = cs.getLocation().toString();
244: FileObject file = lookupFileObject(url);
245: if (file == null) {
246: return super .getPermissions(cs);
247: }
248:
249: FileObject parentLayer = file.getFileSystem()
250: .getParentLayer();
251: if (parentLayer == null) {
252: return super .getPermissions(cs);
253: }
254:
255: Permissions combi = new Permissions();
256: PermissionCollection permCollect = super .getPermissions(cs);
257: copyPermissions(permCollect, combi);
258:
259: for (FileObject parent = parentLayer; parent != null; parent = parent
260: .getFileSystem().getParentLayer()) {
261: final CodeSource parentcs = new CodeSource(parent
262: .getURL(), parent.getContent()
263: .getCertificates());
264: permCollect = super .getPermissions(parentcs);
265: copyPermissions(permCollect, combi);
266: }
267:
268: return combi;
269: } catch (final FileSystemException fse) {
270: throw new SecurityException(fse.getMessage());
271: }
272: }
273:
274: /**
275: * Copies the permissions from src to dest.
276: */
277: protected void copyPermissions(final PermissionCollection src,
278: final PermissionCollection dest) {
279: for (Enumeration elem = src.elements(); elem.hasMoreElements();) {
280: final Permission permission = (Permission) elem
281: .nextElement();
282: dest.add(permission);
283: }
284: }
285:
286: /**
287: * Does a reverse lookup to find the FileObject when we only have the
288: * URL.
289: */
290: private FileObject lookupFileObject(final String name) {
291: final Iterator it = resources.iterator();
292: while (it.hasNext()) {
293: final FileObject object = (FileObject) it.next();
294: if (name.equals(object.getName().getURI())) {
295: return object;
296: }
297: }
298: return null;
299: }
300:
301: /**
302: * Finds the resource with the specified name from the search path.
303: * This returns null if the resource is not found.
304: */
305: protected URL findResource(final String name) {
306: try {
307: final Resource res = loadResource(name);
308: if (res != null) {
309: return res.getURL();
310: }
311: } catch (final Exception mue) {
312: // Ignore
313: // TODO - report?
314: }
315:
316: return null;
317: }
318:
319: /**
320: * Returns an Enumeration of all the resources in the search path
321: * with the specified name.
322: * TODO - Implement this.
323: */
324: protected Enumeration findResources(final String name) {
325: return new Enumeration() {
326: public boolean hasMoreElements() {
327: return false;
328: }
329:
330: public Object nextElement() {
331: return null;
332: }
333: };
334: }
335:
336: /**
337: * Searches through the search path of for the first class or resource
338: * with specified name.
339: */
340: private Resource loadResource(final String name)
341: throws FileSystemException {
342: final Iterator it = resources.iterator();
343: while (it.hasNext()) {
344: final FileObject baseFile = (FileObject) it.next();
345: final FileObject file = baseFile.resolveFile(name,
346: NameScope.DESCENDENT_OR_SELF);
347: if (file.exists()) {
348: return new Resource(name, baseFile, file);
349: }
350: }
351:
352: return null;
353: }
354: }
|