001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb.util;
018:
019: import java.io.Closeable;
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.IOException;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.util.Arrays;
026: import java.util.Collections;
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.TreeMap;
034: import java.util.jar.Attributes;
035: import java.util.jar.JarFile;
036: import java.util.jar.Manifest;
037:
038: import org.apache.openejb.loader.FileUtils;
039: import org.apache.openejb.loader.SystemInstance;
040:
041: public class UrlCache {
042: private static final Logger logger = Logger.getInstance(
043: LogCategory.OPENEJB, UrlCache.class);
044: public static final boolean antiJarLocking;
045: public static final File cacheDir;
046: static {
047: String value = null;
048: for (Map.Entry<Object, Object> entry : System.getProperties()
049: .entrySet()) {
050: if (entry.getKey() instanceof String
051: && entry.getValue() instanceof String) {
052: if ("antiJarLocking".equalsIgnoreCase((String) entry
053: .getKey())) {
054: value = (String) entry.getValue();
055: break;
056: }
057: }
058: }
059:
060: if (value != null) {
061: antiJarLocking = Boolean.valueOf(value);
062: } else {
063: boolean embedded = false;
064: try {
065: FileUtils openejbBase = SystemInstance.get().getBase();
066: embedded = !openejbBase.getDirectory("conf").exists();
067: } catch (IOException e) {
068: }
069:
070: // antiJarLocking is on by default when we are not embedded and running on windows
071: antiJarLocking = !embedded
072: && System.getProperty("os.name", "unknown")
073: .toLowerCase().startsWith("windows");
074: }
075:
076: if (antiJarLocking) {
077: cacheDir = createCacheDir();
078: logger.info("AntiJarLocking enabled. Using URL cache dir "
079: + cacheDir);
080: } else {
081: cacheDir = null;
082: }
083: }
084:
085: private final Map<String, Map<URL, File>> cache = new TreeMap<String, Map<URL, File>>();
086:
087: public synchronized URL[] cacheUrls(String appId, URL[] urls) {
088: if (!antiJarLocking) {
089: return urls;
090: }
091:
092: // the final cached urls
093: LinkedHashSet<URL> cachedUrls = new LinkedHashSet<URL>();
094:
095: // this stack contains the urls to be processed... when manifest class path entries
096: // are added they are added to the top (front) of the stack so manifest order is maintained
097: LinkedList<URL> locationStack = new LinkedList<URL>(Arrays
098: .asList(urls));
099: while (!locationStack.isEmpty()) {
100: URL url = locationStack.removeFirst();
101:
102: // Skip any duplicate urls in the claspath
103: if (cachedUrls.contains(url)) {
104: continue;
105: }
106:
107: // cache the URL
108: File file = cacheUrl(appId, url);
109:
110: // if the url was successfully cached, process it's manifest classpath
111: if (file != null) {
112: try {
113: cachedUrls.add(file.toURL());
114:
115: // push the manifest classpath on the stack (make sure to maintain the order)
116: List<URL> manifestClassPath = getManifestClassPath(
117: url, file);
118: locationStack.addAll(0, manifestClassPath);
119: } catch (MalformedURLException e) {
120: // invalid cache file - this should never happen
121: logger
122: .error(
123: "Error caching url. Original jar file will be used which may result in a file lock: url="
124: + url, e);
125: cachedUrls.add(url);
126: }
127: } else {
128: // URL was not cached - simply pass through the url
129: cachedUrls.add(url);
130: }
131: }
132:
133: return cachedUrls.toArray(new URL[cachedUrls.size()]);
134: }
135:
136: public synchronized void releaseUrls(String appId) {
137: logger.debug("Releasing URLs for application " + appId);
138:
139: Map<URL, File> urlFileMap = cache.remove(appId);
140: if (urlFileMap != null) {
141: for (File file : urlFileMap.values()) {
142: if (file.delete()) {
143: logger.debug("Deleted cached file " + file);
144: } else {
145: logger
146: .debug("Unable to delete cached file "
147: + file);
148: }
149: }
150: }
151: }
152:
153: private synchronized File cacheUrl(String appId, URL url) {
154: File sourceFile;
155: if (!"file".equals(url.getProtocol())) {
156: // todo: download the jar ourselves?
157: // for now return null which means we did not cache
158: return null;
159: } else {
160: // verify file
161: sourceFile = URLs.toFile(url);
162: if (!sourceFile.exists()) {
163: return null;
164: }
165: if (!sourceFile.canRead()) {
166: return null;
167: }
168:
169: // if file is a directory, there is no need to cache
170: if (sourceFile.isDirectory()) {
171: return sourceFile;
172: }
173:
174: // Create absolute file URL
175: sourceFile = sourceFile.getAbsoluteFile();
176: try {
177: url = sourceFile.toURL();
178: } catch (MalformedURLException ignored) {
179: }
180: }
181:
182: // check if file is already cached
183: Map<URL, File> appCache = getAppCache(appId);
184: if (appCache.containsKey(url)) {
185: return appCache.get(url);
186: }
187:
188: // if the file is already in the cache, don't recopy it to the cache dir
189: if (sourceFile.getParentFile().equals(cacheDir)) {
190: // mark it as part of the application, so it cleaned up when the application is undeployed
191: appCache.put(url, sourceFile);
192: return sourceFile;
193: }
194:
195: // generate a nice cache file name
196: String name = sourceFile.getName();
197: int dot = name.lastIndexOf(".");
198: String prefix = name;
199: String suffix = "";
200: if (dot > 0) {
201: prefix = name.substring(0, dot) + "-";
202: suffix = name.substring(dot, name.length());
203: }
204:
205: // copy the file to the cache dir to avoid file locks
206: File cacheFile = null;
207: boolean success;
208: try {
209: cacheFile = File.createTempFile(prefix, suffix, cacheDir);
210: cacheFile.deleteOnExit();
211: success = JarExtractor.copy(sourceFile, cacheFile);
212: } catch (IOException e) {
213: success = false;
214: }
215:
216: if (success) {
217: // add cache file to cache
218: appCache.put(url, cacheFile);
219: logger.debug("Coppied jar file to " + cacheFile);
220: return cacheFile;
221: } else {
222: // clean up failed copy
223: JarExtractor.delete(cacheFile);
224: logger
225: .error("Unable to copy jar into URL cache directory. Original jar file will be used which may result in a file lock: file="
226: + sourceFile);
227: return null;
228: }
229: }
230:
231: private synchronized Map<URL, File> getAppCache(String appId) {
232: Map<URL, File> urlFileMap = cache.get(appId);
233: if (urlFileMap == null) {
234: urlFileMap = new LinkedHashMap<URL, File>();
235: cache.put(appId, urlFileMap);
236: }
237: return urlFileMap;
238: }
239:
240: private List<URL> getManifestClassPath(URL codeSource, File location) {
241: try {
242: // get the manifest, if possible
243: Manifest manifest = loadManifest(location);
244: if (manifest == null) {
245: // some locations don't have a manifest
246: return Collections.emptyList();
247: }
248:
249: // get the class-path attribute, if possible
250: String manifestClassPath = manifest.getMainAttributes()
251: .getValue(Attributes.Name.CLASS_PATH);
252: if (manifestClassPath == null) {
253: return Collections.emptyList();
254: }
255:
256: // build the urls...
257: // the class-path attribute is space delimited
258: LinkedList<URL> classPathUrls = new LinkedList<URL>();
259: for (StringTokenizer tokenizer = new StringTokenizer(
260: manifestClassPath, " "); tokenizer.hasMoreTokens();) {
261: String entry = tokenizer.nextToken();
262: try {
263: // the class path entry is relative to the resource location code source
264: URL entryUrl = new URL(codeSource, entry);
265: classPathUrls.addLast(entryUrl);
266: } catch (MalformedURLException ignored) {
267: // most likely a poorly named entry
268: }
269: }
270: return classPathUrls;
271: } catch (IOException ignored) {
272: // error opening the manifest
273: return Collections.emptyList();
274: }
275: }
276:
277: private Manifest loadManifest(File location) throws IOException {
278: if (location.isDirectory()) {
279: File manifestFile = new File(location,
280: "META-INF/MANIFEST.MF");
281:
282: if (manifestFile.isFile() && manifestFile.canRead()) {
283: FileInputStream in = null;
284: try {
285: in = new FileInputStream(manifestFile);
286: Manifest manifest = new Manifest(in);
287: return manifest;
288: } finally {
289: close(in);
290: }
291: }
292: } else {
293: JarFile jarFile = new JarFile(location);
294: try {
295: Manifest manifest = jarFile.getManifest();
296: return manifest;
297: } finally {
298: close(jarFile);
299: }
300: }
301: return null;
302: }
303:
304: private static File createCacheDir() {
305: try {
306: FileUtils openejbBase = SystemInstance.get().getBase();
307:
308: File cacheDir = null;
309: // if we are not embedded, cache (temp) dir is under base dir
310: if (openejbBase.getDirectory("conf").exists()) {
311: try {
312: cacheDir = openejbBase.getDirectory("temp");
313: } catch (IOException e) {
314: }
315: }
316:
317: // if we are embedded, tmp dir is in the system tmp dir
318: if (cacheDir == null) {
319: cacheDir = File.createTempFile("OpenEJB-temp-", "");
320: }
321:
322: // if the cache dir already exists, clear it out
323: if (cacheDir.exists()) {
324: deleteDir(cacheDir);
325: }
326:
327: // create the cache dir if it no longer exists
328: cacheDir.mkdirs();
329:
330: return cacheDir;
331: } catch (IOException e) {
332: throw new RuntimeException(e);
333: }
334: }
335:
336: /**
337: * Delete the specified directory, including all of its contents and
338: * subdirectories recursively.
339: *
340: * @param dir File object representing the directory to be deleted
341: */
342: public static void deleteDir(File dir) {
343: if (dir == null)
344: return;
345:
346: File[] fileNames = dir.listFiles();
347: if (fileNames != null) {
348: for (File file : fileNames) {
349: if (file.isDirectory()) {
350: deleteDir(file);
351: } else {
352: if (file.delete()) {
353: logger.debug("Deleted file " + file);
354: } else {
355: logger.debug("Unable to delete file " + file);
356: }
357:
358: }
359: }
360: }
361: if (dir.delete()) {
362: logger.debug("Deleted file " + dir);
363: } else {
364: logger.debug("Unable to delete file " + dir);
365: }
366: }
367:
368: private static void close(Closeable closeable) {
369: if (closeable != null) {
370: try {
371: closeable.close();
372: } catch (IOException ignored) {
373: }
374: }
375: }
376:
377: private static void close(JarFile closeable) {
378: if (closeable != null) {
379: try {
380: closeable.close();
381: } catch (IOException ignored) {
382: }
383: }
384: }
385: }
|