001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 Cougaar Software, Inc
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: *
026: * CHANGE RECORD
027: * -
028: */
029:
030: package org.cougaar.util.jar;
031:
032: import java.io.File;
033: import java.io.FileFilter;
034: import java.io.FileNotFoundException;
035: import java.io.FileOutputStream;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.net.JarURLConnection;
039: import java.net.URI;
040: import java.net.URL;
041: import java.security.GeneralSecurityException;
042: import java.util.ArrayList;
043: import java.util.Enumeration;
044: import java.util.Iterator;
045: import java.util.List;
046: import java.util.ListIterator;
047: import java.util.Map;
048: import java.util.jar.JarEntry;
049: import java.util.jar.JarFile;
050:
051: import org.cougaar.bootstrap.SystemProperties;
052: import org.cougaar.util.ConfigFinder;
053: import org.cougaar.util.Configuration;
054:
055: /**
056: * JarConfigFinder provides utilities to search for a named file in several specified
057: * locations, returning the first location where a file by that name is found.
058: * Files are found and opened by the open() method.
059: * JarConfigFinder searches an ordered list of configuration paths to locate files.
060: * Each element in the list is a URL pointing to a file or a directory, and may
061: * reference one of the following elements:
062: * <ul>
063: * <li>A simple file</li>
064: * <li>A signed or unsigned jar file in a file system or available through an HTTP connection</li>
065: * <li>A local directory</li>
066: * </ul>
067: * The configuration path is a semi-colon ordered list of these URL elements.
068: * The search is performed by looking at the URL elements from left to right.
069: * <ul>
070: * <li>If the element is a simple file and the name matches the requested file,
071: * the file is returned.</li>
072: * <li>If the element is a jar file, the JarConfigFinder attempts to locate
073: * a file in the jar file that matches the name of the requested file.
074: * The jar file can be opened in a file system or through an HTTP connection.</li>
075: * <li>If the element is a directory in a file system, the JarConfigFinder
076: * attempts to locate a matching (unsigned) file in that directory.
077: * The JarConfigFinder also looks up all jar files contained in that
078: * directory and tries to find a matching file in one of those jar files.</li>
079: * </ul>
080: * Properties:
081: * <ul>
082: * <li>If the <b>org.cougaar.util.jar.jarFilesOnly</b> is set to true,
083: * JarConfigFinder returns files contained in JAR files only</li>
084: * <li>If the <b>org.cougaar.config.signedOnly</b> property is set to true,
085: * the SecureConfigFinder returns only files that have been signed.</b>
086: * </ul>
087: */
088: public class JarConfigFinder extends ConfigFinder {
089: private static final String ORG_COUGAAR_WORKSPACE = "org.cougaar.workspace";
090:
091: private static final String JAR_FILES_ONLY = "org.cougaar.util.jar.jarFilesOnly";
092:
093: private static long DELAY_TO_RELEASE_JAR_FILES = 5 * 60 * 1000;
094:
095: /** A list of URLs pointing to JAR files containing resource files.
096: * Files requested by a client are searched in those jar files.
097: */
098: private List _jarFileCache = new ArrayList();
099:
100: /** A list of path to look for JAR files.
101: * This list is initially populated by using the configuration
102: * from the constructor. It may be augmented by adding Jar files
103: * found in the config path
104: */
105: private List _configPathList;
106:
107: /**
108: * A list of URLs pointing to Jar files that have been released.
109: * This allows JarFiles to be reclaimed by the garbage collector.
110: */
111: private List _releasedJarFileUrls = new ArrayList();
112:
113: /** A directory to store files extracted from JAR files, so that we
114: * can return a File reference to the client.
115: */
116: protected File _jarFileCacheDirectory;
117:
118: /** A directory to extract files from JAR files, before they are
119: * moved to the _jarFileCacheDirectory.
120: */
121: protected File _tmpDirectory;
122:
123: /** Parent directory to _tmpDirectory and _jarFileCacheDirectory,
124: * specified by getTmpBaseDirectoryName(). If null, we will
125: * use the system-defined tmp directory (see File.createTempFile).
126: **/
127: protected File _tmpBaseDirectory;
128:
129: private boolean _jarFilesOnly;
130:
131: /**
132: * Alias for JarConfigFinder({@link Configuration#getConfigPath()}, {@link Configuration#getDefaultProperties()})
133: */
134: public JarConfigFinder() {
135: this (Configuration.getConfigPath(), Configuration
136: .getDefaultProperties());
137: }
138:
139: /**
140: * Alias for JarConfigFinder(path, {@link Configuration#getDefaultProperties()})
141: */
142: public JarConfigFinder(String path) {
143: this (path, Configuration.getDefaultProperties());
144: }
145:
146: /**
147: * Alias for JarConfigFinder(null, path, props)
148: */
149: public JarConfigFinder(String path, Map props) {
150: this (null, path, props);
151: }
152:
153: /**
154: * Alias for JarConfigFinder(module, path, Configuration.getDefaultProperties())
155: */
156: public JarConfigFinder(String module, String path) {
157: this (module, path, Configuration.getDefaultProperties());
158: }
159:
160: /**
161: * Constructs a JarConfigFinder that will first search within the specified module,
162: * and then in the directories on the given search path, using the given
163: * Property substitutions.
164: * When searching the given module, we search the following 4 directories (if defined) before any other directories:
165: * <ul>
166: * <li>$INSTALL/$module/configs/$CONFIG</li>
167: * <li>$INSTALL/$module/configs</li>
168: * <li>$INSTALL/$module/data/$CONFIG</li>
169: * <li>$INSTALL/$module/data</li>
170: * </ul>
171: *
172: * @param module Name of the module to use for module-specific configs. If null, no module-specific paths are added.
173: * @param path Configuration path string. If null, defaults to Configuration.getConfigPath()
174: * @param props Properties to use for configpath variable substitutions
175: */
176: public JarConfigFinder(String module, String path, Map props) {
177: super (module, path, props);
178: createJarFileCacheDirectory();
179:
180: _configPathList = new ArrayList();
181: _configPathList.addAll(getConfigPath());
182:
183: // Allow locating files in all files by default
184: _jarFilesOnly = SystemProperties.getBoolean(JAR_FILES_ONLY);
185:
186: if (getLogger().isDebugEnabled()) {
187: getLogger().debug("jar Files only: " + jarFilesOnly());
188: Iterator it = _configPathList.iterator();
189: String s = "";
190: while (it.hasNext()) {
191: s = s + " - " + ((URL) it.next()).toString();
192: }
193: getLogger().debug("Config path:" + s);
194: }
195: launchJarInfoCleanupThread();
196: }
197:
198: ////////////////////////////////////////////////////////////////////
199: // BEGIN ConfigFinder overloaded methods
200:
201: /**
202: * Locate an actual file in the config path.
203: * @param aFilename The name of a file being searched
204: */
205: public File locateFile(String aFilename) {
206: if (getLogger().isDebugEnabled()) {
207: getLogger().debug("locateFile:" + aFilename);
208: }
209: URL aUrl = resolveUrl(aFilename);
210: if (aUrl == null) {
211: return null;
212: }
213: if (getLogger().isDebugEnabled()) {
214: getLogger().debug("Found:" + aUrl.toString());
215: }
216: File f = null;
217: if (aUrl.getProtocol().startsWith("jar")) {
218: // The file is in a Jar file. We cannot return a File handle
219: // directly, so we copy the file in a temporary directory
220: // copyFileToTempDirectory checks for invalid signatures.
221: try {
222: f = copyFileToTempDirectory(aUrl, aFilename);
223: } catch (Exception e) {
224: getLogger().warn(
225: "Unable to copy to temp directory: "
226: + aFilename + ". Reason: " + e);
227: }
228: } else {
229: try {
230: f = new File(new URI(aUrl.toString()));
231: } catch (Exception e) {
232: getLogger().warn(
233: "Unable to get file handle for " + aFilename);
234: }
235: }
236: return f;
237: }
238:
239: /**
240: * Opens an InputStream to access the named file. The file is sought
241: * in all the places specified in configPath.
242: * The file being searched should be specified as one of:
243: * <ul>
244: * <li>The name of the file being searched, e.g. just the last name in the pathname's name sequence.
245: * For example, a component might lookup a file by specifying "config.txt"</li>
246: * <li>The last few elements in the pathname of the file being searched.
247: * For example, a component might lookup a file by specifying "org/cougaar/config.txt"</li>
248: * <li>The full URL of the file in the JAR file
249: * For example, a component might lookup a file by specifying
250: * "jar:file://usr/local/cougaar/configs.jar!org/cougaar/config.txt"</li>
251: * </ul>
252: * @param aURL The name of a file being searched
253: * @throws FileNotFoundException if the resource cannot be found.
254: **/
255: public InputStream open(String aURL) throws IOException {
256: if (getLogger().isDebugEnabled()) {
257: getLogger().debug("open:" + aURL);
258: }
259: URL aUrl = resolveUrl(aURL);
260: if (getLogger().isDebugEnabled()) {
261: getLogger().debug(
262: "Found:" + (aUrl != null ? aUrl.toString() : null));
263: }
264: if (aUrl == null) {
265: throw new FileNotFoundException(
266: "Resource cannot be found: " + aURL);
267: } else {
268: // The resolveUrl() method checks that the
269: // signature was valid, so we do not check again here
270: return aUrl.openStream();
271: }
272: }
273:
274: /**
275: * Attempt to find the URL which would be opened by the open method.
276: * Note that this must actually attempt to open the various URLs
277: * under consideration, so this is <em>not</em> an inexpensive operation.
278: * The file being searched should be specified as one of:
279: * <ul>
280: * <li>The name of the file being searched, e.g. just the last name in the pathname's name sequence.
281: * For example, a component might lookup a file by specifying "config.txt"</li>
282: * <li>The last few elements in the pathname of the file being searched.
283: * For example, a component might lookup a file by specifying "org/cougaar/config.txt"</li>
284: * <li>The full URL of the file in the JAR file
285: * For example, a component might lookup a file by specifying
286: * "jar:file://usr/local/cougaar/configs.jar!org/cougaar/config.txt"</li>
287: * </ul>
288: * @param aURL The name of a file being searched
289: **/
290: public URL find(String aURL) throws IOException {
291: // The resolveUrl() method checks that the
292: // signature was valid, so we do not check again
293: URL theURL = resolveUrl(aURL);
294: return theURL;
295: }
296:
297: // END ConfigFinder overloaded methods
298: ///////////////////////////////////////////////////////////////////
299:
300: /**
301: * Resolve a logical reference to a URL
302: * @param aFileName the name of a file to be resolved
303: * @return null if no file can be found at that location
304: */
305: protected URL resolveUrl(String aFileName) {
306: URL theURL = null;
307:
308: synchronized (urlCache) {
309: if (getLogger().isDebugEnabled()) {
310: getLogger().debug(
311: "Looking up " + aFileName + " in local cache ("
312: + urlCache.size() + " elements)");
313: }
314: // First, search in the cache
315: theURL = (URL) urlCache.get(aFileName);
316: }
317:
318: if (theURL != null) {
319: if (getLogger().isDebugEnabled()) {
320: getLogger().debug(
321: "Found " + aFileName + " in local cache");
322: }
323: } else {
324: // Second, search the file in the list of JAR files
325: if (getLogger().isDebugEnabled()) {
326: getLogger().debug(
327: "Looking up " + aFileName
328: + " in Jar file list ("
329: + _jarFileCache.size() + " jar files)");
330: }
331: synchronized (_jarFileCache) {
332: refreshJarFileCache();
333: ListIterator it = _jarFileCache.listIterator();
334: while (it.hasNext()) {
335: JarFileInfo entry = (JarFileInfo) it.next();
336: theURL = locateFileInJarFile(aFileName, entry);
337: if (theURL != null) {
338: if (getLogger().isDebugEnabled()) {
339: getLogger().debug(
340: "Found " + aFileName
341: + " in Jar file list");
342: }
343: }
344: }
345: }
346: }
347:
348: if (theURL == null) {
349: // Third, the list of JAR files may have not been updated yet.
350: // Update the list of jar files by looking in the configPath and
351: // finding jar files. Remove jar files from configPath as they
352: // are added to _jarFileCache.
353: List configPath = null;
354: List pathsToRemove = null;
355: synchronized (_configPathList) {
356: configPath = new ArrayList(_configPathList);
357: }
358: if (getLogger().isDebugEnabled()) {
359: getLogger().debug(
360: "Looking up " + aFileName + " in config path ("
361: + configPath.size() + " elements)");
362: }
363: for (ListIterator it = configPath.listIterator(); (it
364: .hasNext() && (theURL == null));) {
365: URL base = (URL) it.next();
366: theURL = locateFileInPathElement(base, aFileName);
367: // plan to remove the path, so that we don't look it
368: // up again the next time we search.
369: if (getLogger().isDebugEnabled()) {
370: getLogger()
371: .debug("Removing " + base + " from path");
372: }
373: if (pathsToRemove == null) {
374: pathsToRemove = new ArrayList();
375: }
376: pathsToRemove.add(base);
377: /*
378: for (int i = 0 ; i < configPath.size() ; i++) {
379: getLogger().debug(configPath.get(i).toString());
380: }
381: */
382: }
383: if (pathsToRemove != null) {
384: synchronized (_configPathList) {
385: _configPathList.removeAll(pathsToRemove);
386: }
387: }
388: }
389:
390: if (theURL == null) {
391: if (getLogger().isDebugEnabled()) {
392: File f = new File(aFileName);
393: getLogger()
394: .debug(
395: "Looking up " + aFileName
396: + " - Accept absolute path:"
397: + acceptAbsoluteFileNames()
398: + " - isAbsolutePath:"
399: + f.isAbsolute()
400: + " - Jar Files only:"
401: + jarFilesOnly());
402: }
403: // The URL may be an absolute file name
404: if (acceptAbsoluteFileNames()) {
405: File f = new File(aFileName);
406: if (f.isAbsolute() && !jarFilesOnly()) {
407: try {
408: if (isValidUrl(f.toURL())) {
409: addFileEntryToCache(f.getName(), f.toURL());
410: // Is there a match?
411: if (f.getPath().equals(aFileName)) {
412: theURL = f.toURL();
413: }
414: }
415: } catch (Exception e) {
416: getLogger().warn(
417: "Unable to get URL for " + f.getPath());
418: }
419: }
420: }
421: }
422:
423: // Verify the integrity of the input stream.
424: try {
425: verifyInputStream(theURL);
426: } catch (Exception e) {
427: // Do not return the URL if the integrity of the data could not
428: // be verified.
429: theURL = null;
430: }
431: return theURL;
432: }
433:
434: /** Return a JarFile if the file is really a Jar file.
435: * Return null otherwise.
436: */
437: protected JarFile getJarFile(File aFile) {
438: try {
439: JarFile aJarFile = new JarFile(aFile);
440: return aJarFile;
441: } catch (Exception e) {
442: // This is not a Jar file, or the file could not be read
443: return null;
444: }
445: }
446:
447: protected void appendJarFiles(URL[] jarFiles) {
448: for (int i = 0; i < jarFiles.length; i++) {
449: appendJarFile(jarFiles[i]);
450: }
451: }
452:
453: /** Return a pathname to use as the directory argument for File.createTempFile.
454: * to construct _tmpBaseDirectory.
455: * If null, then we'll use the system-defined tmp directory location
456: * The default implementation returns the value of the system property "org.cougaar.workspace"
457: * if defined, otherwise null.
458: */
459: protected String getTmpBaseDirectoryName() {
460: /*
461: // for instance, the SecureConfigFinder might define this as:
462: return SystemProperties.getProperty("org.cougaar.workspace") + File.separator +
463: "security" + File.separator +
464: "jarconfig" + File.separator +
465: SystemProperties.getProperty("org.cougaar.node.name");
466: */
467: // by default, use /tmp or the equivalent
468: return SystemProperties.getProperty(ORG_COUGAAR_WORKSPACE)
469: + File.separator + "jarfiles";
470: }
471:
472: /** create a set of uniquely-named temporary directories for
473: * our use.
474: */
475: protected void createJarFileCacheDirectory() {
476: // Create temporary directory to store files
477: try {
478: String base = getTmpBaseDirectoryName();
479: File baseF;
480: if (base == null) {
481: baseF = null;
482: } else {
483: baseF = new File(base);
484: if (!baseF.exists()) {
485: baseF.mkdirs();
486: }
487: }
488:
489: // creates a regular ".lck" file. We'll use the base name
490: // of the created file to create the directory (without the .lck suffix)
491: File fL = File.createTempFile("jarconfig", ".lck", baseF);
492: fL.deleteOnExit();
493:
494: // Now we create a base directory using the created temp file
495: // as a lock and a suggestion for a name
496: String tmpPath;
497: {
498: String tmp = fL.getCanonicalPath();
499: tmpPath = tmp.substring(0, tmp.length() - 4); // trim off the ".lck"
500: }
501:
502: _tmpBaseDirectory = new File(tmpPath);
503: if (_tmpBaseDirectory.exists()) {
504: getLogger().warn(
505: "tmpBaseDirectory " + tmpPath
506: + " already exists!");
507: }
508: _tmpBaseDirectory.mkdirs();
509: _tmpBaseDirectory.deleteOnExit();
510:
511: _jarFileCacheDirectory = new File(tmpPath + File.separator
512: + "jarFileCache");
513: _jarFileCacheDirectory.mkdirs();
514: _jarFileCacheDirectory.deleteOnExit();
515:
516: _tmpDirectory = new File(tmpPath + File.separator + "tmp");
517: _tmpDirectory.mkdirs();
518: _tmpDirectory.deleteOnExit();
519:
520: } catch (Exception e) {
521: getLogger().warn("Unable to create temporary directory", e);
522: }
523: }
524:
525: private void deleteDirectory(File file) {
526: if (file == null || !file.exists()) {
527: return;
528: }
529: if (file.isFile()) {
530: file.delete();
531: } else {
532: File subfiles[] = file.listFiles();
533: for (int i = 0; i < subfiles.length; i++) {
534: deleteDirectory(subfiles[i]);
535: }
536: }
537: }
538:
539: /**
540: * Copy a file contained in a Jar file so that it can be opened using
541: * a <class>File</class> handle.
542: */
543: protected File copyFileToTempDirectory(URL aUrl, String aFilename)
544: throws IOException, GeneralSecurityException {
545: if (getLogger().isDebugEnabled()) {
546: getLogger().debug("Copying " + aUrl + " to temp directory");
547: }
548:
549: File tempFile = File.createTempFile("tmpFile", ".tmp",
550: _tmpDirectory);
551: FileOutputStream fos = new FileOutputStream(tempFile);
552: JarURLConnection juc = (JarURLConnection) aUrl.openConnection();
553: juc.setUseCaches(false);
554: InputStream is = juc.getInputStream();
555: int v = 0;
556: while ((v = is.read()) != -1) {
557: fos.write(v);
558: }
559: fos.close();
560: is.close();
561:
562: File newFile = new File(_jarFileCacheDirectory, aFilename);
563: boolean isRenamed = tempFile.renameTo(newFile);
564: if (isRenamed) {
565: return newFile;
566: } else {
567: // Remove the temp file
568: tempFile.delete();
569: return null;
570: }
571: }
572:
573: protected JarFileInfo appendJarFile(URL jarFile) {
574: try {
575: if (!jarFile.getProtocol().equals("jar")) {
576: // The multi-parameter URL constructor does not like
577: // the "jar:file" protocol, but it works with a one-parameter
578: // constructor
579: String s = "jar:" + jarFile.getProtocol() + ":"
580: + jarFile.getHost();
581: if (jarFile.getPort() != -1) {
582: s = s + ":" + jarFile.getPort();
583: }
584: s = s + jarFile.getPath() + "!/";
585: jarFile = new URL(s);
586: if (getLogger().isDebugEnabled()) {
587: getLogger().debug(
588: "Append Jar File: " + jarFile.toString());
589: }
590: }
591:
592: JarFileInfo entry = new JarFileInfo(jarFile);
593:
594: verifyJarFile(entry.getJarFile());
595: synchronized (_jarFileCache) {
596: _jarFileCache.add(entry);
597: }
598: return entry;
599: } catch (Exception e) {
600: getLogger().warn(
601: "Unable to add entry: " + jarFile.toString(), e);
602: return null;
603: }
604: }
605:
606: /**
607: * Attempts to locate a file in a JAR file.
608: * @param aFileName the name of a file being searched.
609: * @param entry a cache entry representing a JAR file
610: * @return the URL of the file in the JAR file, if an entry was found.
611: * null if no entry was found in the JAR file.
612: */
613: protected URL locateFileInJarFile(String aFileName,
614: JarFileInfo entry) {
615: URL theURL = null;
616: if (entry == null) {
617: return null;
618: }
619: if (getLogger().isDebugEnabled()) {
620: getLogger().debug(
621: "Locate file " + aFileName + " in "
622: + entry.getJarFileURL() + " Processed: "
623: + entry.isJarFileProcessed());
624: }
625: if (entry.isJarFileProcessed()) {
626: // The JAR file has already been processed. Therefore,
627: // we should have already looked in the cache for that Jar file.
628: return null;
629: }
630: JarFile jarFile = entry.getJarFile();
631: Enumeration e = jarFile.entries();
632: while (e.hasMoreElements()) {
633: JarEntry jarEntry = (JarEntry) e.nextElement();
634: if (jarEntry.isDirectory()) {
635: continue;
636: }
637: String entryFullName = jarEntry.getName();
638: File aFile = new File(entryFullName);
639: String entryName = aFile.getName();
640:
641: String s = entry.getJarFileURL().toString() + entryFullName;
642: try {
643: URL aURL = new URL(s);
644: addFileEntryToCache(entryName, aURL);
645: if (entryName.equals(aFileName)) {
646: theURL = aURL;
647: }
648: } catch (Exception ex) {
649: getLogger().warn("Unexpected exception: ", ex);
650: }
651: }
652: entry.setJarFileProcessed(true);
653: return theURL;
654: }
655:
656: /**
657: * Check the integrity of a jar file. Do nothing in the base
658: * implementation.
659: */
660: protected void verifyJarFile(JarFile aJarFile)
661: throws GeneralSecurityException {
662: }
663:
664: /**
665: * Verify the integrity of the data contained at a URL.
666: * Some integrity issues might be discovered late when reading
667: * an input stream. For example, digest errors are discovered
668: * when the entire stream has been read. This gives the opportunity
669: * for a secure file finder to verify the data before the stream
670: * is returned to the caller.
671: *
672: * Does nothing in the default implementation, but should typically be
673: * defined in a derived class.
674: * @param aURL the URL to check.
675: * @exception IOException if an IO Exception occurs while opening the stream
676: * @exception GeneralSecurityException if there was a problem while checking
677: * the integrity of the input stream.
678: */
679: protected void verifyInputStream(URL aURL) throws IOException,
680: GeneralSecurityException {
681: }
682:
683: private void addFileEntryToCache(String aFileName, URL aURL) {
684: synchronized (urlCache) {
685: if (!urlCache.containsKey(aFileName)) {
686: // getLogger().debug("Adding mapping: " + aFileName + "<=>"
687: // + aURL.toString());
688: urlCache.put(aFileName, aURL);
689: }
690: }
691: }
692:
693: /**
694: * @param base - The path element where to search files.
695: * @param aFileName - The name of a file to search.
696: *
697: * If aFileName is null, then no search is performed. The path
698: * element is still added to the cache.
699: */
700: protected URL locateFileInPathElement(URL base, String aFileName) {
701: URL theURL = null;
702: if (getLogger().isDebugEnabled()) {
703: getLogger().debug(
704: "locateFileInPathElement:" + aFileName + " in "
705: + base.toString());
706: }
707: if (base.getProtocol().equals("file")
708: || base.getProtocol().equals("jar:file")) {
709: theURL = locateFileInFileElement(base, aFileName);
710: } else if (base.getProtocol().equals("http")) {
711: theURL = locateFileInHttpElement(base, aFileName);
712: }
713: return theURL;
714: }
715:
716: protected URL locateFileInFileElement(URL base, String aFileName) {
717: URL theURL = null;
718: File aFile = new File(base.getFile());
719: if (aFile.exists()) {
720: if (aFile.isFile()) {
721: // This is a file. Return it if there is a match
722: // and it is ok to return unsigned jar files.
723: JarFile aJarFile = getJarFile(aFile);
724: if (aJarFile == null) {
725: // This is not a Jar file, or the file could not be read
726: if (!jarFilesOnly() && aFileName != null) {
727: try {
728: if (isValidUrl(aFile.toURL())) {
729: addFileEntryToCache(aFile.getName(),
730: aFile.toURL());
731: // Is there a match?
732: if (aFile.getPath().equals(aFileName)) {
733: theURL = aFile.toURL();
734: }
735: }
736: } catch (Exception e) {
737: getLogger().warn(
738: "Unable to get URL for "
739: + aFile.getPath());
740: }
741: }
742: } else {
743: // This is a Jar file.
744: // Add the new jar file to the list of jar files.
745: try {
746: JarFileInfo entry = appendJarFile(aFile.toURL());
747: if (aFileName != null) {
748: theURL = locateFileInJarFile(aFileName,
749: entry);
750: }
751: } catch (Exception e) {
752: getLogger().warn(
753: "Unable to get URL for "
754: + aFile.getPath());
755: }
756: }
757: } else if (aFile.isDirectory()) {
758: // This is a directory. Attempt to find jar files
759: // in that directory.
760:
761: File jarFiles[] = aFile.listFiles(new FileFilter() {
762: public boolean accept(File pathname) {
763: return pathname.getName().endsWith(".jar");
764: }
765: });
766: for (int i = 0; i < jarFiles.length; i++) {
767: JarFile aJar = getJarFile((File) jarFiles[i]);
768: if (aJar != null) {
769: // Add the new jar file to the list of jar files.
770: try {
771: JarFileInfo entry = appendJarFile(jarFiles[i]
772: .toURL());
773: if (aFileName != null) {
774: URL aURL = locateFileInJarFile(
775: aFileName, entry);
776: if (aURL != null && theURL == null) {
777: theURL = aURL;
778: }
779: }
780: } catch (Exception e) {
781: getLogger().warn(
782: "Unable to get URL for "
783: + jarFiles[i]);
784: }
785: }
786: }
787: // Also try to find simple files in that directory
788: // (if allowed by the configuration)
789: if (!jarFilesOnly()) {
790: File files[] = aFile.listFiles(new FileFilter() {
791: public boolean accept(File pathname) {
792: return true;
793: }
794: });
795: for (int i = 0; i < files.length; i++) {
796: try {
797: File confFile = files[i];
798: if (isValidUrl(confFile.toURL())) {
799: addFileEntryToCache(confFile.getName(),
800: confFile.toURL());
801: // Is there a match?
802: if (confFile.getName()
803: .equals(aFileName)) {
804: theURL = confFile.toURL();
805: }
806: }
807: } catch (Exception e) {
808: getLogger().warn(
809: "Unable to get URL for "
810: + aFile.getPath());
811: }
812: }
813: }
814: }
815: }
816: if (getLogger().isDebugEnabled()) {
817: getLogger().debug(
818: "Searched " + aFileName + " under "
819: + base.toString() + ". Found: " + theURL);
820: }
821: return theURL;
822: }
823:
824: protected URL locateFileInHttpElement(URL base, String aFileName) {
825: URL theURL = null;
826: // First, try to open the file as a Jar URL connection
827: try {
828: JarURLConnection juc = (JarURLConnection) base
829: .openConnection();
830: juc.setUseCaches(false);
831: JarFile jf = juc.getJarFile();
832: // This is a Jar file.
833: // Add the new jar file to the list of jar files.
834: JarFileInfo entry = appendJarFile(juc.getURL());
835: if (aFileName != null) {
836: theURL = locateFileInJarFile(aFileName, entry);
837: }
838: } catch (Exception e) {
839: // That wasn't a Jar file
840: }
841: if (theURL == null) {
842: // Process the given URL as a standard file
843: if (base.getFile().equals(aFileName)) {
844: theURL = base;
845: }
846: }
847: return theURL;
848: }
849:
850: /**
851: * Determines if a simple configuration file can be loaded.
852: * When signed jar files are used, files that are not in signed jar files
853: * are not loaded. However, there might be exceptions to the rule and
854: * specific files may be authorized even if they are not signed.
855: * By default, the base JarConfigFinder always allows unsigned jar files.
856: *
857: * @param aUrl The URL of a configuration file
858: */
859: protected boolean isValidUrl(URL aUrl) {
860: return true;
861: }
862:
863: /**
864: * Determines if configuration files must be stored in signed jar files.
865: *
866: * @return true if configuration files must be in signed jar files only
867: */
868: protected boolean jarFilesOnly() {
869: return _jarFilesOnly;
870: }
871:
872: /**
873: * Determines if ConfigFinder client may specify absolute file names.
874: */
875: protected boolean acceptAbsoluteFileNames() {
876: return true;
877: }
878:
879: protected void launchJarInfoCleanupThread() {
880: Runnable r = new Runnable() {
881: public void run() {
882: while (true) {
883: try {
884: Thread.sleep(DELAY_TO_RELEASE_JAR_FILES);
885: } catch (Exception e) {
886: }
887: synchronized (_jarFileCache) {
888: Iterator it = _jarFileCache.iterator();
889: while (it.hasNext()) {
890: JarFileInfo entry = (JarFileInfo) it.next();
891: long age = System.currentTimeMillis()
892: - entry.getCreationTime();
893: if (age > DELAY_TO_RELEASE_JAR_FILES) {
894: synchronized (_releasedJarFileUrls) {
895: //System.out.println("Releasing " + entry.getJarFileURL());
896: _releasedJarFileUrls.add(entry
897: .getJarFileURL());
898: }
899: it.remove();
900: }
901: }
902: }
903: /*
904: System.out.println("Size of Jar cache: " + _jarFileCache.size()
905: + " - Size of released files: "
906: + _releasedJarFileUrls.size());
907: */
908: }
909: }
910: };
911: Thread t = new Thread(r);
912: t.start();
913: }
914:
915: /**
916: * Add Jar files back to the cache of Jar files.
917: */
918: protected void refreshJarFileCache() {
919: synchronized (_releasedJarFileUrls) {
920: Iterator it = _releasedJarFileUrls.iterator();
921: while (it.hasNext()) {
922: URL entry = (URL) it.next();
923: appendJarFile(entry);
924: it.remove();
925: }
926: }
927: }
928: }
|