View Javadoc

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         // 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 }