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: */package org.apache.geronimo.kernel.classloader;
017:
018: import java.io.File;
019: import java.io.FileNotFoundException;
020: import java.io.IOException;
021: import java.net.MalformedURLException;
022: import java.net.URL;
023: import java.util.ArrayList;
024: import java.util.Arrays;
025: import java.util.Collections;
026: import java.util.Enumeration;
027: import java.util.LinkedHashMap;
028: import java.util.LinkedHashSet;
029: import java.util.LinkedList;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.StringTokenizer;
033: import java.util.jar.Attributes;
034: import java.util.jar.Manifest;
035:
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038:
039: /**
040: * @version $Rev: 583532 $ $Date: 2007-10-10 09:34:01 -0700 (Wed, 10 Oct 2007) $
041: */
042: public class UrlResourceFinder implements ResourceFinder {
043: private final Object lock = new Object();
044:
045: private static final Log log = LogFactory
046: .getLog(UrlResourceFinder.class);
047: private final LinkedHashSet<URL> urls = new LinkedHashSet<URL>();
048: private final LinkedHashMap<URL, ResourceLocation> classPath = new LinkedHashMap<URL, ResourceLocation>();
049: private final LinkedHashSet<File> watchedFiles = new LinkedHashSet<File>();
050:
051: private boolean destroyed = false;
052:
053: public UrlResourceFinder() {
054: }
055:
056: public UrlResourceFinder(URL[] urls) {
057: addUrls(urls);
058: }
059:
060: public void destroy() {
061: synchronized (lock) {
062: if (destroyed) {
063: return;
064: }
065: destroyed = true;
066: urls.clear();
067: for (ResourceLocation resourceLocation : classPath.values()) {
068: resourceLocation.close();
069: }
070: classPath.clear();
071: }
072: }
073:
074: public ResourceHandle getResource(String resourceName) {
075: synchronized (lock) {
076: if (destroyed) {
077: return null;
078: }
079: for (Map.Entry<URL, ResourceLocation> entry : getClassPath()
080: .entrySet()) {
081: ResourceLocation resourceLocation = entry.getValue();
082: ResourceHandle resourceHandle = resourceLocation
083: .getResourceHandle(resourceName);
084: if (resourceHandle != null
085: && !resourceHandle.isDirectory()) {
086: return resourceHandle;
087: }
088: }
089: }
090: return null;
091: }
092:
093: public URL findResource(String resourceName) {
094: synchronized (lock) {
095: if (destroyed) {
096: return null;
097: }
098: for (Map.Entry<URL, ResourceLocation> entry : getClassPath()
099: .entrySet()) {
100: ResourceLocation resourceLocation = entry.getValue();
101: ResourceHandle resourceHandle = resourceLocation
102: .getResourceHandle(resourceName);
103: if (resourceHandle != null) {
104: return resourceHandle.getUrl();
105: }
106: }
107: }
108: return null;
109: }
110:
111: public Enumeration findResources(String resourceName) {
112: synchronized (lock) {
113: return new ResourceEnumeration(
114: new ArrayList<ResourceLocation>(getClassPath()
115: .values()), resourceName);
116: }
117: }
118:
119: public void addUrl(URL url) {
120: addUrls(Collections.singletonList(url));
121: }
122:
123: public URL[] getUrls() {
124: synchronized (lock) {
125: return urls.toArray(new URL[urls.size()]);
126: }
127: }
128:
129: /**
130: * Adds an array of urls to the end of this class loader.
131: * @param urls the URLs to add
132: */
133: protected void addUrls(URL[] urls) {
134: addUrls(Arrays.asList(urls));
135: }
136:
137: /**
138: * Adds a list of urls to the end of this class loader.
139: * @param urls the URLs to add
140: */
141: protected void addUrls(List<URL> urls) {
142: synchronized (lock) {
143: if (destroyed) {
144: throw new IllegalStateException(
145: "UrlResourceFinder has been destroyed");
146: }
147:
148: boolean shouldRebuild = this .urls.addAll(urls);
149: if (shouldRebuild) {
150: rebuildClassPath();
151: }
152: }
153: }
154:
155: private LinkedHashMap<URL, ResourceLocation> getClassPath() {
156: assert Thread.holdsLock(lock) : "This method can only be called while holding the lock";
157:
158: for (File file : watchedFiles) {
159: if (file.canRead()) {
160: rebuildClassPath();
161: break;
162: }
163: }
164:
165: return classPath;
166: }
167:
168: /**
169: * Rebuilds the entire class path. This class is called when new URLs are added or one of the watched files
170: * becomes readable. This method will not open jar files again, but will add any new entries not alredy open
171: * to the class path. If any file based url is does not exist, we will watch for that file to appear.
172: */
173: private void rebuildClassPath() {
174: assert Thread.holdsLock(lock) : "This method can only be called while holding the lock";
175:
176: // copy all of the existing locations into a temp map and clear the class path
177: Map<URL, ResourceLocation> existingJarFiles = new LinkedHashMap<URL, ResourceLocation>(
178: classPath);
179: classPath.clear();
180:
181: LinkedList<URL> locationStack = new LinkedList<URL>(urls);
182: try {
183: while (!locationStack.isEmpty()) {
184: URL url = locationStack.removeFirst();
185:
186: // Skip any duplicate urls in the classpath
187: if (classPath.containsKey(url)) {
188: continue;
189: }
190:
191: // Check is this URL has already been opened
192: ResourceLocation resourceLocation = existingJarFiles
193: .remove(url);
194:
195: // If not opened, cache the url and wrap it with a resource location
196: if (resourceLocation == null) {
197: try {
198: File file = cacheUrl(url);
199: resourceLocation = createResourceLocation(url,
200: file);
201: } catch (FileNotFoundException e) {
202: // if this is a file URL, the file doesn't exist yet... watch to see if it appears later
203: if ("file".equals(url.getProtocol())) {
204: File file = new File(url.getPath());
205: watchedFiles.add(file);
206: continue;
207:
208: }
209: } catch (IOException ignored) {
210: // can't seem to open the file... this is most likely a bad jar file
211: // so don't keep a watch out for it because that would require lots of checking
212: // Dain: We may want to review this decision later
213: continue;
214: } catch (UnsupportedOperationException ex) {
215: // the protocol for the JAR file's URL is not supported. This can occur when
216: // the jar file is embedded in an EAR or CAR file. Proceed but log the message.
217: log.error(ex);
218: continue;
219: }
220: }
221:
222: // add the jar to our class path
223: classPath.put(resourceLocation.getCodeSource(),
224: resourceLocation);
225:
226: // push the manifest classpath on the stack (make sure to maintain the order)
227: List<URL> manifestClassPath = getManifestClassPath(resourceLocation);
228: locationStack.addAll(0, manifestClassPath);
229: }
230: } catch (Error e) {
231: destroy();
232: throw e;
233: }
234:
235: for (ResourceLocation resourceLocation : existingJarFiles
236: .values()) {
237: resourceLocation.close();
238: }
239: }
240:
241: protected File cacheUrl(URL url) throws IOException {
242: if (!"file".equals(url.getProtocol())) {
243: // download the jar
244: throw new UnsupportedOperationException(
245: "Only local file jars are supported " + url);
246: }
247:
248: File file = new File(url.getPath());
249: if (!file.exists()) {
250: throw new FileNotFoundException(file.getAbsolutePath());
251: }
252: if (!file.canRead()) {
253: throw new IOException("File is not readable: "
254: + file.getAbsolutePath());
255: }
256: return file;
257: }
258:
259: protected ResourceLocation createResourceLocation(URL codeSource,
260: File cacheFile) throws IOException {
261: if (!cacheFile.exists()) {
262: throw new FileNotFoundException(cacheFile.getAbsolutePath());
263: }
264: if (!cacheFile.canRead()) {
265: throw new IOException("File is not readable: "
266: + cacheFile.getAbsolutePath());
267: }
268:
269: ResourceLocation resourceLocation;
270: if (cacheFile.isDirectory()) {
271: // DirectoryResourceLocation will only return "file" URLs within this directory
272: // do not user the DirectoryResourceLocation for non file based urls
273: resourceLocation = new DirectoryResourceLocation(cacheFile);
274: } else {
275: resourceLocation = new JarResourceLocation(codeSource,
276: cacheFile);
277: }
278: return resourceLocation;
279: }
280:
281: private List<URL> getManifestClassPath(
282: ResourceLocation resourceLocation) {
283: try {
284: // get the manifest, if possible
285: Manifest manifest = resourceLocation.getManifest();
286: if (manifest == null) {
287: // some locations don't have a manifest
288: return Collections.EMPTY_LIST;
289: }
290:
291: // get the class-path attribute, if possible
292: String manifestClassPath = manifest.getMainAttributes()
293: .getValue(Attributes.Name.CLASS_PATH);
294: if (manifestClassPath == null) {
295: return Collections.EMPTY_LIST;
296: }
297:
298: // build the urls...
299: // the class-path attribute is space delimited
300: URL codeSource = resourceLocation.getCodeSource();
301: LinkedList<URL> classPathUrls = new LinkedList<URL>();
302: for (StringTokenizer tokenizer = new StringTokenizer(
303: manifestClassPath, " "); tokenizer.hasMoreTokens();) {
304: String entry = tokenizer.nextToken();
305: try {
306: // the class path entry is relative to the resource location code source
307: URL entryUrl = new URL(codeSource, entry);
308: classPathUrls.addLast(entryUrl);
309: } catch (MalformedURLException ignored) {
310: // most likely a poorly named entry
311: }
312: }
313: return classPathUrls;
314: } catch (IOException ignored) {
315: // error opening the manifest
316: return Collections.EMPTY_LIST;
317: }
318: }
319: }
|