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 }