1 /**
2 *
3 * Copyright 2005 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * 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.geronimo.kernel.classloader;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.FileNotFoundException;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.LinkedHashSet;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.StringTokenizer;
35 import java.util.jar.Attributes;
36 import java.util.jar.Manifest;
37 import java.util.jar.JarFile;
38
39 /**
40 * @version $Rev: 410741 $ $Date: 2006-05-31 21:35:48 -0700 (Wed, 31 May 2006) $
41 */
42 public class UrlResourceFinder implements ResourceFinder {
43 private final Object lock = new Object();
44
45 private final LinkedHashSet urls = new LinkedHashSet();
46 private final LinkedHashMap classPath = new LinkedHashMap();
47 private final LinkedHashSet watchedFiles = new LinkedHashSet();
48
49 private boolean destroyed = false;
50
51 public UrlResourceFinder() {
52 }
53
54 public UrlResourceFinder(URL[] urls) {
55 addUrls(urls);
56 }
57
58 public void destroy() {
59 synchronized (lock) {
60 if (destroyed) {
61 return;
62 }
63 destroyed = true;
64 urls.clear();
65 for (Iterator iterator = classPath.values().iterator(); iterator.hasNext();) {
66 ResourceLocation resourceLocation = (ResourceLocation) iterator.next();
67 resourceLocation.close();
68 }
69 classPath.clear();
70 }
71 }
72
73 public ResourceHandle getResource(String resourceName) {
74 synchronized (lock) {
75 if (destroyed) {
76 return null;
77 }
78 for (Iterator iterator = getClassPath().entrySet().iterator(); iterator.hasNext();) {
79 Map.Entry entry = (Map.Entry) iterator.next();
80 ResourceLocation resourceLocation = (ResourceLocation) entry.getValue();
81 ResourceHandle resourceHandle = resourceLocation.getResourceHandle(resourceName);
82 if (resourceHandle != null && !resourceHandle.isDirectory()) {
83 return resourceHandle;
84 }
85 }
86 }
87 return null;
88 }
89
90 public URL findResource(String resourceName) {
91 synchronized (lock) {
92 if (destroyed) {
93 return null;
94 }
95 for (Iterator iterator = getClassPath().entrySet().iterator(); iterator.hasNext();) {
96 Map.Entry entry = (Map.Entry) iterator.next();
97 ResourceLocation resourceLocation = (ResourceLocation) entry.getValue();
98 ResourceHandle resourceHandle = resourceLocation.getResourceHandle(resourceName);
99 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
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
180 if (classPath.containsKey(url)) {
181 continue;
182 }
183
184
185 ResourceLocation resourceLocation = (ResourceLocation) existingJarFiles.remove(url);
186
187
188 if (resourceLocation == null) {
189 try {
190 File file = cacheUrl(url);
191 resourceLocation = createResourceLocation(url, file);
192 } catch (FileNotFoundException e) {
193
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
202
203
204 continue;
205 }
206 }
207
208
209 classPath.put(resourceLocation.getCodeSource(), resourceLocation);
210
211
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
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
253
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
264 Manifest manifest = resourceLocation.getManifest();
265 if (manifest == null) {
266
267 return Collections.EMPTY_LIST;
268 }
269
270
271 String manifestClassPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
272 if (manifestClassPath == null) {
273 return Collections.EMPTY_LIST;
274 }
275
276
277
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
284 URL entryUrl = new URL(codeSource, entry);
285 classPathUrls.addLast(entryUrl);
286 } catch (MalformedURLException ignored) {
287
288 }
289 }
290 return classPathUrls;
291 } catch (IOException ignored) {
292
293 return Collections.EMPTY_LIST;
294 }
295 }
296 }