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