001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: ClassPathEntry.java,v 1.3 2007-10-19 10:35:46 sinisa Exp $
022: */
023:
024: package com.lutris.classloader;
025:
026: // lutris packages
027: // v. strahinja, 24 sep 2002
028: import java.io.File;
029: import java.io.FileNotFoundException;
030: import java.io.IOException;
031: import java.net.MalformedURLException;
032: import java.net.URL;
033: import java.util.zip.ZipFile;
034:
035: import com.lutris.logging.LogChannel;
036:
037: /**
038: * <P><H3>Summary:</H3>
039: * <P>A class path entry which is a URL representing either a local or a
040: * remote directory or zip file. Manages all the details for zip file support.
041: * For example, local zip files (once opened) are kept open for the lifetime
042: * of the entry for increased performance.
043: *
044: * <P><H3>Features:</H3>
045: * <UL>
046: * <LI>Supports absolute and relative file names
047: * <LI>Creates URL entry from given String, File, or URL
048: * <LI>Adds "/" to end of URL for directories if necessary
049: * <LI>Knows if URL is zip local or remote
050: * <LI>Knows if URL is a zip file or directory
051: * <LI>Keeps ZipFile open if the zip file is local
052: * </UL>
053: *
054: * <P><B>Note: "zip files" are files with ".zip" or ".jar" extensions.</B>
055: *
056: * <P><H3>Class Path Entries:</H3>
057: * <P>Example valid class path entries are:
058: * <PRE>
059: * <EM>Files and directories on the local file system</EM>
060: * ../../java/classes
061: * /users/kristen/java/classes
062: * /users/kristen/java/classes/
063: * /users/kristen/java/zipfiles/MyClasses.zip
064: * /users/kristen/java/jarfiles/MyClasses.jar
065: * file:///users/kristen/java/classes
066: * file://localhost/users/kristen/java/classes
067: * <BR>
068: * <EM>Files and directories on a remote file system
069: * (must be in URL format)</EM>
070: * ftp://www.foo.com/pub/java/classes
071: * file://www.foo.com/pub/java/classes/
072: * http://www.foo.com/web/java/classes/
073: * file://www.foo.com:8080/pub/java/zipfiles/MyClasses.zip
074: * http://www.foo.com:8080/web/java/jarfiles/MyClasses.jar
075: * </PRE>
076: *
077: * <P>Note that the location of the entry includes the protocol, the host name,
078: * and the port while the file name is everything else. For example,
079: *
080: * <PRE>
081: * http://www.foo.com:8080/web/java/jarfiles/MyClasses.jar
082: * </PRE>
083: * has the form [location][name] or
084: * <PRE>
085: * [http://www.foo.com:8080/][/web/java/jarfiles/MyClasses.jar]
086: * </PRE>
087: * so the location is "http://www.foo.com:8080/" and the name is
088: * "/web/java/jarfiles/MyClasses.jar".
089: *
090: * <P>Note that the two references
091: * <PRE>
092: * /users/kristen/java/classes/
093: * file:///users/kristen/java/classes/
094: * </PRE>
095: * represent the same directory on a Unix machine, and
096: * <PRE>
097: * C|/windows/java/classes/
098: * file:///C|/windows/java/classes/
099: * </PRE>
100: * are equivalent directories on a Windows box.
101: *
102: * <P>But the two references
103: * <PRE>
104: * /users/kristen/java/classes/
105: * file://monet.lutris.com/users/kristen/java/classes/
106: * </PRE>
107: * are not equivalent even if the directory
108: * <EM>/users/kristen/java/classes/</EM> lives
109: * on the machine named <EM>monet.lutris.com</EM> and all development
110: * is on this machine. Why? Because the web (browser?) protocol is different
111: * for URLs with host information and those without. If no host is
112: * specified, the file is assumed to be on the local machine and the
113: * path is determined from the <EM>ROOT</EM> of the machine. If the
114: * host is specified, then the <EM>ftp protocol</EM> is used and the path
115: * is determined from the <EM>ftp ROOT</EM> (e.g. /users/ftp/) rather
116: * than the machine's ROOT. Thus, on a machine that support's anonymous
117: * ftp, the following two directories are the same:
118: * <PRE>
119: * /users/ftp/pub/classes/
120: * file://picasso.lutris.com/pub/classes/
121: * </PRE>
122: * assuming the development is being done on <EM>picasso.lutris.com</EM>.
123: *
124: * <H3>System Class Path Entries</H3>
125: *
126: * <P>The system class path is the system-dependent path of directories
127: * and files (e.g. CLASSPATH on Unix and Windows) used by the system
128: * class loader to load classes. Valid system class path entries are
129: * directories and zip files, specified by absolute path or relative path
130: * on the system. Any valid system class path entry is also valid to
131: * create a <CODE>ClassPathEntry</CODE>.
132: *
133: * <H3>Example</H3>
134: *
135: * <P>Here is an example of how to use a ClassPathEntry:
136: * <PRE>
137: * ClassPathEntry entry = new ClassPathEntry("/home/java/Test.jar");
138: * System.out.println("My entry URL is " + entry.get());
139: * System.out.println("My entry string is " + entry.toString());
140: * System.out.println("My entry name " + entry.getName());
141: * System.out.println("My entry location " + entry.getLocation());
142: * System.out.println("My entry is a zip file? " + entry.isZipFile());
143: * Resource resource = entry.getResource("foo.gif");
144: * </PRE>
145: *
146: * @author Kristen Pol, Lutris Technologies
147: * @version $Revision : 1.0 $
148: * @see com.lutris.classloader.Resource
149: * @see java.net.URL
150: */
151: public class ClassPathEntry {
152: /*
153: * This could be made into an abstract class and be subclassed by
154: * LocalDirEntry, LocalZipEntry, RemoteDirEntry, and RemoteZipEntry.
155: */
156:
157: // private data members
158: /** The class path entry represented by a URL. */
159: private URL entryURL = null;
160:
161: /** The ZipFile for this class path entry, if appropriate. */
162: private ZipFile zipFile = null;
163:
164: /** Is logging enabled? */
165: private boolean loggingEnabled = false;
166:
167: /** Log channel to write messages to */
168: // v. strahinja, 24 sep 2002
169: private LogChannel logChannel;
170: // private Logger logger;
171:
172: /** Numeric log level number for LOGLEVEL string */
173: // v. strahinja, 24 sep 2002
174: private int logLevel;
175: // private Level logLevel;
176:
177: /** Cache this information as it is accessed quite a bit */
178: private boolean isZipFile;
179:
180: // constructors
181:
182: /**
183: * Constructs class path entry with specified String.
184: * The String is assumed to be either a directory or zip file on
185: * the local machine or a remote machine, which can be represented by the
186: * absolute file name, the relative file name, or a URL.
187: *
188: * <P>Examples:
189: * <PRE>
190: * ClassPathEntry("../../java/classes");
191: * ClassPathEntry("/users/kristen/java/classes");
192: * ClassPathEntry("/users/kristen/java/classes/");
193: * ClassPathEntry("/users/kristen/java/MyClasses.zip");
194: * ClassPathEntry("/users/kristen/java/MyClasses.jar");
195: * ClassPathEntry("ftp://www.foo.com/pub/classes/");
196: * ClassPathEntry("file://www.foo.com/web/classes/");
197: * ClassPathEntry("http://www.foo.com/web/classes/");
198: * ClassPathEntry("http://www.foo.com:8080/web/classes/");
199: * ClassPathEntry("http://www.foo.com/web/classes/MyClasses.jar");
200: * </PRE>
201: *
202: * @param entry The class path entry.
203: * @param loadLogChannel The log channel.
204: */
205: // v. strahinja, 24 sep 2002
206: public ClassPathEntry(String entry, LogChannel loadLogChannel) {
207: // v. strahinja, 24 sep 2002
208: this (convertEntryToURL(entry), loadLogChannel);
209: // public ClassPathEntry(String entry, Logger loadLogger) {
210: // this(convertEntryToURL(entry), loadLogger);
211: }
212:
213: /**
214: * Constructs class path entry with specified File.
215: * The File can represent a directory or zip file on the local machine.
216: *
217: * <P>Examples:
218: * <PRE>
219: * ClassPathEntry(new File("../../java/classes"));
220: * ClassPathEntry(new File("/users/kristen/java/classes"));
221: * ClassPathEntry(new File("/users/kristen/java/classes/"));
222: * ClassPathEntry(new File("/users/kristen/java/MyClasses.zip"));
223: * ClassPathEntry(new File("/users/kristen/java/MyClasses.jar"));
224: * </PRE>
225: *
226: * @param entry The class path entry.
227: * @param loadLogChannel The log channel.
228: */
229: // v. strahinja, 24 sep 2002
230: public ClassPathEntry(File entry, LogChannel loadLogChannel) {
231: // v. strahinja, 24 sep 2002
232: this (convertEntryToURL(entry), loadLogChannel);
233: // public ClassPathEntry(File entry, Logger loadLogger) {
234: // this(convertEntryToURL(entry), loadLogger);
235: }
236:
237: /**
238: * Constructs class path entry with specified URL.
239: * The URL can represent a directory or zip file on the local machine
240: * or a remote machine.
241: *
242: * <P>Examples:
243: * <PRE>
244: * ClassPathEntry(new URL("ftp://www.foo.com/pub/classes/"));
245: * ClassPathEntry(new URL("file://www.foo.com/web/classes/"));
246: * ClassPathEntry(new URL("http://www.foo.com/web/classes/"));
247: * ClassPathEntry(new URL("http://www.foo.com:8080/web/classes/"));
248: * ClassPathEntry(new URL("http://www.foo.com/web/MyClasses.jar"));
249: * </PRE>
250: *
251: * @param entry The class path entry.
252: * @param loadLogChannel The log channel.
253: */
254: // v. strahinja, 24 sep 2002
255: public ClassPathEntry(URL entry, LogChannel loadLogChannel) {
256: // public ClassPathEntry(URL entry, Logger loadLogger) {
257: /*
258: * This constructor actually does all the work, because all
259: * other constructors call this one.
260: */
261: try {
262: // v. strahinja, 24 sep 2002
263: this .logChannel = loadLogChannel;
264: // v. strahinja, 24 sep 2002
265: if (logChannel != null) {
266: // v. strahinja, 24 sep 2002
267: this .logLevel = logChannel
268: .getLevel(MultiClassLoader.LOG_LEVEL);
269: // v. strahinja, 24 sep 2002
270: loggingEnabled = logChannel.isEnabled(logLevel);
271: // this.logger = loadLogger;
272: // if (logger != null) {
273: //sinisa 24.08.2003 logLevel = logger.getLevel();
274: // logLevel = Level.DEBUG;
275: // if (logLevel == null) {
276: // logLevel = Level.DEBUG;
277: // }
278: // loggingEnabled = logger.isEnabledFor(logLevel);
279: }
280: this .entryURL = new URL(cleanUpURL(entry).toString());
281: } catch (MalformedURLException mue) {
282: if (loggingEnabled) {
283: // v. strahinja, 24 sep 2002
284: logChannel.write(logLevel, "Illegal class path entry: "
285: + entry);
286: // logger.log(logLevel, "Illegal class path entry: " + entry);
287: }
288: this .entryURL = null;
289: }
290: initIsZipFile();
291: }
292:
293: // public methods
294:
295: /**
296: * Gets class path entry set previously by constructor.
297: *
298: * @return the class path entry represented by a URL.
299: */
300: public URL getURL() {
301: return entryURL;
302: }
303:
304: /**
305: * Gets file name of class path entry. For example, if the entry is
306: * "http://www.foo.com:8080/java/classes/MyClasses.jar", the name is
307: * "/java/classes/MyClasses.jar". The beginning slash does not mean
308: * that its an absolute file name on its host machine. The file name
309: * is always <EM>relative</EM> to the location.
310: *
311: * @return the file name of the class path entry.
312: */
313: public String getName() {
314: // XXX: Don't use a beginning slash? (kp)
315: return entryURL.getFile();
316: }
317:
318: /**
319: * Gets location of class path entry. For example, if the entry is
320: * "http://www.foo.com:8080/java/classes/MyClasses.jar", the location is
321: * "http://www.foo.com:8080/".
322: *
323: * @return the file name for the class path entry.
324: */
325: public String getLocation() {
326: if (entryURL.getPort() != -1) {
327: return entryURL.getProtocol() + "://" + entryURL.getHost()
328: + ":" + entryURL.getPort() + "/";
329: } else {
330: return entryURL.getProtocol() + "://" + entryURL.getHost()
331: + "/";
332: }
333: }
334:
335: /**
336: * Stringifies class path entry set previously by constructor.
337: *
338: * @return the class path entry represented by a stringified URL.
339: */
340: public String toString() {
341: if (entryURL != null) {
342: return entryURL.toString();
343: } else {
344: return "null";
345: }
346: }
347:
348: /**
349: * Gets resource with the specified name from class path entry.
350: * The result will be null if the resource can not be found.
351: *
352: * @param name The file name associated with the resource.
353: * @return the resource from the class path entry, or null if it is
354: * not found.
355: * @see Resource
356: */
357: public Resource getResource(String name) {
358: name = convertSlashes(name);
359: // FIXME: This is rather inefficient, as it creates a
360: // resource and throws an exception just to check a path
361: // entry. Probably need to have a ClassPathEntry per resource
362: // type .
363: Resource resource = null;
364: try {
365: if (isDirectory() && isLocal()) {
366: // v. strahinja, 24 sep 2002
367: resource = new LocalDirResource(name, this , logChannel);
368: // resource = new LocalDirResource(name, this, logger);
369: } else if (isZipFile() && isLocal()) {
370: // v. strahinja, 24 sep 2002
371: resource = new LocalZipResource(name, this , logChannel);
372: // resource = new LocalZipResource(name, this, logger);
373: } else if (isDirectory() && isRemote()) {
374: // FIXME: Test RemoteDirResource, then add (kp)
375: // resource = new RemoteDirResource(name, this, logChannel);
376: if (loggingEnabled) {
377: // v. strahinja, 24 sep 2002
378: logChannel.write(logLevel,
379: "Cannot get remote directory resource.");
380: // logger.log(logLevel, "Cannot get remote directory resource.");
381: }
382: resource = null;
383: } else if (isZipFile() && isRemote()) {
384: // FIXME: Test RemoteZipResource, then add (kp)
385: // resource = new RemoteZipResource(name, this, logChannel);
386: if (loggingEnabled) {
387: // v. strahinja, 24 sep 2002
388: logChannel.write(logLevel,
389: "Cannot get remote jar file resource.");
390: // logger.log(logLevel, "Cannot get remote jar file resource.");
391: }
392: resource = null;
393: }
394: } catch (FileNotFoundException e) {
395: if (loggingEnabled) {
396: // Don't log stack trace, as this error is a `normal'
397: // when searching a path.
398: // v. strahinja, 24 sep 2002
399: logChannel.write(logLevel, "File not found: " + name
400: // logger.log(logLevel, "File not found: " + name
401: + ": " + e.getClass().getName() + ": "
402: + e.getMessage());
403: }
404: resource = null;
405: }
406: return resource;
407: }
408:
409: /**
410: * Determines if class path entry is a zip file. Anything ending
411: * in ".zip" or ".jar" is considered a zip file.
412: *
413: * @return true if the class path entry is a zip file, false if it is not.
414: */
415: public boolean isZipFile() {
416: return isZipFile;
417: //return (toString().endsWith(".jar") || toString().endsWith(".zip"));
418: }
419:
420: /**
421: * Called at init time or when ever the entryURL can change.
422: */
423: private void initIsZipFile() {
424: isZipFile = (toString().endsWith(".jar") || toString()
425: .endsWith(".zip"));
426: }
427:
428: /**
429: * Determines if class path entry is a directory. Anything that is
430: * not a zip file is considered a directory.
431: *
432: * @return true if the class path entry is a directory, false if it is not.
433: * @see #isZipFile
434: */
435: public boolean isDirectory() {
436: return (!isZipFile());
437: }
438:
439: /**
440: * Determines if class path entry is local. Anything that has
441: * no host name or a host name of "localhost" is considered local.
442: *
443: * @return true if the class path entry is remote, false if it is not.
444: */
445: public boolean isLocal() {
446: String host = entryURL.getHost();
447: if (host.equals("") || host.equalsIgnoreCase("localhost")
448: || host.equals("127.0.0.1")) {
449: return true;
450: }
451: return false;
452: }
453:
454: /**
455: * Determines if class path entry is remote. Anything that is not
456: * local is considered remote.
457: *
458: * @return true if the class path entry is remote, false if it is not.
459: * @see #isLocal
460: */
461: public boolean isRemote() {
462: return (!isLocal());
463: }
464:
465: /**
466: * Determines if specified class path entry is equal to this entry.
467: * The entries are considered equal if the URLs are the same.
468: *
469: * @param cpe The class path entry to check for equality.
470: * @return true if the class path entry is equal, false if it is not.
471: */
472: public boolean equals(Object o) {
473: if (o instanceof ClassPathEntry) {
474: return entryURL.equals(((ClassPathEntry) o).getURL());
475: }
476: return false;
477: }
478:
479: /**
480: * Gets zip file associated with class path entry if appropriate. If
481: * a zip file is not found, null will be returned.
482: *
483: * @return the zip file associated with this class path entry if
484: * found, null if not.
485: */
486: public ZipFile getZipFile() {
487: if (zipFile == null) {
488: return doGetZipFile();
489: }
490: return zipFile;
491: }
492:
493: /**
494: * Gets zip file associated with class path entry if appropriate. If
495: * a zip file is not found, null will be returned.
496: *
497: * @return the zip file associated with this class path entry if
498: * found, null if not.
499: */
500: private synchronized ZipFile doGetZipFile() {
501: if (zipFile == null) {
502: if (isZipFile() && isLocal()) {
503: try {
504: zipFile = new ZipFile(getName());
505: } catch (IOException e) {
506: if (loggingEnabled) {
507: // v. strahinja, 24 sep 2002
508: logChannel.write(logLevel,
509: "Cannot create zip file " + getName()
510: + ".", e);
511: // logger.log(logLevel,
512: // "Cannot create zip file " + getName() + ".", e);
513: }
514: zipFile = null;
515: }
516: } else {
517: zipFile = null;
518: }
519: }
520: return zipFile;
521: }
522:
523: // private helper methods
524:
525: /**
526: * Cleans up URL by fixing slashes. All back slashes are converted to
527: * forward slashes ("/"). A slash is added to the end of the URL if
528: * it is a directory and does not already have an ending slash.
529: *
530: * @param url The URL to clean up.
531: * @return a cleaned up version of the URL.
532: */
533: private static URL cleanUpURL(URL url) {
534: URL newURL = null;
535: try {
536: //FIXME: This code converts `.' to `/'.
537: String urlString = url.toString();
538: if (!urlString.endsWith("/") && !urlString.endsWith(".jar")
539: && !urlString.endsWith(".zip")) {
540: newURL = new URL(convertSlashes(url.toString() + "/"));
541: } else {
542: newURL = new URL(convertSlashes(url.toString()));
543: }
544: } catch (MalformedURLException e) {
545: // FIXME: Log. Cannot use logChannel because it's not static.
546: // FIXME: should just throw exception..
547: newURL = null;
548: }
549: return newURL;
550: }
551:
552: /**
553: * Converts Object to URL. Only URLs, Files, and Strings
554: * can be successfully converted. All other object types will
555: * result in a null return.
556: *
557: * @param object The object to convert.
558: * @return the URL representing the object, or null if the conversion
559: * was not successful.
560: */
561: private static URL convertEntryToURL(Object object) {
562: if (object instanceof java.net.URL) {
563: return (URL) object;
564: } else if (object instanceof java.lang.String) {
565: return convertEntryToURL((String) object);
566: } else if (object instanceof java.io.File) {
567: return convertEntryToURL(object.toString());
568: } else {
569: // FIXME: Log. Cannot use logChannel because it's not static.
570: // FIXME: should just throw exception.
571: return null;
572: }
573: }
574:
575: /**
576: * Converts String to URL.
577: *
578: * @param string The string to convert.
579: * @return the URL representation of the given string.
580: */
581: private static URL convertEntryToURL(String string) {
582: try {
583: return new URL(string);
584: } catch (MalformedURLException e) {
585: try {
586: String absPath = (new File(string)).getAbsolutePath();
587: // Convert directory separator to "/", since only "/" is a
588: // legal separator in URLs
589: absPath = absPath.replace(File.separatorChar, '/');
590: // If an absolute path doesn't start with a '/', assume it's
591: // windows and add it.
592: if (!absPath.startsWith("/")) {
593: absPath = "/" + absPath;
594: }
595: return new URL("file://" + absPath);
596: } catch (MalformedURLException e2) {
597: // FIXME: Log. Cannot use logChannel because it's not static.
598: return null;
599: }
600: }
601: }
602:
603: /**
604: * Converts all system path separators to "/". This is to accommodate
605: * both Windows and Unix for URL generation and searching zip files.
606: *
607: * @param string The string to convert.
608: * @return the string with all slashes converted.
609: */
610: private static String convertSlashes(String string) {
611: string = string.replace(File.separatorChar, '/');
612: return string.replace('\\', '/');
613: }
614: }
|