001: /*
002: * Copyright 2001 Sun Microsystems, Inc. All rights reserved.
003: * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
004: */
005:
006: package com.sun.portal.desktop.context;
007:
008: import java.io.File;
009: import java.io.IOException;
010: import java.io.FilenameFilter;
011: import java.util.Map;
012: import java.util.HashSet;
013: import java.util.HashMap;
014: import java.util.Iterator;
015: import java.util.Enumeration;
016: import java.util.zip.ZipFile;
017: import java.util.zip.ZipEntry;
018:
019: /**
020: * A singleton class that caches information needed by the provider classloader
021: * @author ramak
022: * @version
023: */
024: public class ClassInfoCache {
025:
026: private static String providerClassBaseDir = null;
027: private static ClassInfoCache instance;
028: private static Map classInfoMap = new HashMap(100);
029: private static Map jarMap = new HashMap(5);
030: private static long CACHE_VALIDATE_INTERVAL = 1209600000;
031: private long lastValidate = 0;
032:
033: private ClassInfoCache() {
034: }
035:
036: /**
037: * Gets an instance of ClassInfoCache
038: *
039: * @param providerBaseDir Directory from which provider classes will be loaded as String
040: * @return instance of ClassInfoCache
041: */
042: public static synchronized ClassInfoCache getInstance(
043: String providerBaseDir) {
044: if (providerBaseDir == null) {
045: return null;
046: }
047: if (instance == null) {
048: providerClassBaseDir = providerBaseDir;
049: instance = new ClassInfoCache();
050: }
051:
052: return instance;
053: }
054:
055: /**
056: * Gets the information related to a class existing in the Provider class
057: * base directory either in the form of a class file or a Jar file
058: *
059: * @param className Name of the class as String
060: * @return File handle to the class file or the Jar file in which this class
061: * exists. The return value is null if the class does not exist in
062: * the cache.
063: */
064: public File getHandle(String className) {
065: File fHandle = null;
066: CacheEntry cacheEntry = (CacheEntry) classInfoMap
067: .get(className);
068: if (cacheEntry != null) {
069: fHandle = cacheEntry.fileHandle;
070: }
071: return fHandle;
072: }
073:
074: /**
075: * Gets the information related to a class existing in the Provider class
076: * base directory either in the form of a class file or a Jar file
077: *
078: * @param className Name of the class as String
079: * @return Last modified time the class file or the Jar file in which this
080: * class exists. This is the last modified time when the class was
081: * loaded.
082: */
083:
084: public long getModifiedTime(String className) {
085: long modTime = 0;
086: CacheEntry cacheEntry = (CacheEntry) classInfoMap
087: .get(className);
088: if (cacheEntry != null) {
089: modTime = cacheEntry.lastModTime;
090: }
091: return modTime;
092:
093: }
094:
095: /**
096: * Sets the information related to a class into the cache
097: *
098: * @param className Name of the class as a String
099: * @param fromJar true if the class exists in a a Jar file under the
100: * Provider Class base directory
101: * @param fileHandle handle to the class file if existing as a class file
102: * of handle to the Jar file if the class is inside a Jarfile
103: */
104: public void setClassInfo(String className, boolean fromJar,
105: File fileHandle) {
106:
107: CacheEntry entry = (CacheEntry) classInfoMap.get(className);
108: // If classInfo exists in the cache and the class exists as file in the
109: // filesystem then its info is not overwritten with the info
110: // of the same class existing in a Jarfile.
111: if (entry != null && !entry.fromJar
112: && (entry.fileHandle).exists()) {
113: return;
114: }
115:
116: CacheEntry newInfo = new CacheEntry(fromJar, fileHandle);
117: synchronized (classInfoMap) {
118: classInfoMap.put(className, newInfo);
119: }
120:
121: }
122:
123: /**
124: * Removes the information related to a class from the Cache
125: * @param className Name of the class as String
126: */
127: public void removeClassInfo(String className) {
128:
129: synchronized (classInfoMap) {
130: // Remove the info from the classInfoMap
131: CacheEntry ce = (CacheEntry) classInfoMap.remove(className);
132: // If the class originated from a Jar file
133: // remove the handle to Jar file from jarMap
134: if (ce != null && ce.fromJar) {
135: synchronized (jarMap) {
136: jarMap.remove(ce.fileHandle);
137: }
138: }
139: }
140:
141: }
142:
143: /**
144: * Gets the File handle to a JarFile to which the class exists under the
145: * Provider class base directory
146: * @param className Name of the class as String
147: * @return File handle to a Jar File if the class exists in a Jar else
148: * <CODE>null</CODE>
149: * @exception IOException throws I/O Exception
150: */
151: public File getJarFileHandle(String className) throws IOException {
152: File jarFileHandle = null;
153: CacheEntry entry = (CacheEntry) classInfoMap.get(className);
154: // If the cache returns a null then call the updateCacheWithJars
155: // method to update the cache with the new Jars
156: if (entry == null) {
157: updateCacheWithJars();
158: entry = (CacheEntry) classInfoMap.get(className);
159: }
160:
161: if (entry != null) {
162: if (entry.fromJar && (entry.fileHandle).canRead()) {
163: jarFileHandle = entry.fileHandle;
164: }
165: }
166:
167: return jarFileHandle;
168: }
169:
170: /**
171: * Sets the information related to classes existing in JarFile into cache
172: * @param jarFileHandle File handle to the Jar File
173: * @param jarContent An Enumeration containing the Zip Entries in Jarfile
174: */
175: public void setClassInfoFromJar(File jarFileHandle,
176: Enumeration jarContent) {
177: while (jarContent.hasMoreElements()) {
178: String className;
179: ZipEntry zEntry = (ZipEntry) jarContent.nextElement();
180: String jarEntry = zEntry.getName();
181: // For each entry in the Jar file check if it is a class file
182: // If enty is a class file then prepare its class name
183: if (jarEntry != null && jarEntry.endsWith(".class")) {
184: String tempStr = jarEntry.substring(0, jarEntry
185: .lastIndexOf(".class"));
186: //all the platforms zipEntry will return only '/' for
187: //denoting the entries directory structure, so '/' is hardcoded
188: className = tempStr.replace('/', '.');
189: setClassInfo(className, true, jarFileHandle);
190: }
191: }
192:
193: }
194:
195: /**
196: * Updates the cache with the contents of all the Jarfiles in the Provider
197: * class base directory.
198: * @exception IOException throws I/O Exception
199: */
200: private void updateCacheWithJars() throws IOException {
201:
202: long validateInterval = System.currentTimeMillis()
203: - lastValidate;
204: // If the cache validate interval has expired call validate method
205: if (validateInterval > CACHE_VALIDATE_INTERVAL) {
206: validateCache();
207: lastValidate = System.currentTimeMillis();
208: }
209:
210: // get the list of all jar files in the base search directory
211: SuffixFilenameFilter sff = new SuffixFilenameFilter(".jar");
212: File[] jars = new File(providerClassBaseDir).listFiles(sff);
213:
214: if (jars != null) {
215: for (int i = 0; i < jars.length; i++) {
216: File this Jar = jars[i];
217: if (this Jar.isFile() && this Jar.canRead()) {
218:
219: Long prevJarModTime = (Long) jarMap.get(this Jar);
220: long jarLastModTime = this Jar.lastModified();
221:
222: if (prevJarModTime == null
223: || (jarLastModTime > prevJarModTime
224: .longValue())) {
225:
226: ZipFile zFile = new ZipFile(this Jar);
227: setClassInfoFromJar(this Jar, zFile.entries());
228: zFile.close();
229: synchronized (jarMap) {
230: jarMap.put(this Jar,
231: new Long(jarLastModTime));
232: }
233: }
234: }
235: }
236: }
237:
238: }
239:
240: /**
241: * This method is called before an update is done on the cache to remove
242: * information about stale classes in the classInfoMap. The cache validation
243: * is done at regular intervals.
244: */
245: private void validateCache() {
246: HashSet classNameSet = new HashSet(classInfoMap.keySet());
247: Iterator it = classNameSet.iterator();
248: while (it.hasNext()) {
249: String className = (String) it.next();
250: CacheEntry cEntry = (CacheEntry) classInfoMap
251: .get(className);
252: // If the file handle for the class file or the Jar file to which
253: // this class belongs in not valid anymore then remove the info
254: // from the classInfo map
255: if (!((cEntry.fileHandle).canRead())) {
256: removeClassInfo(className);
257: if (cEntry.fromJar) {
258: // If the file handle belongs to a Jar file then remove this
259: // jar file handle from the Map of available Jarfiles
260: synchronized (jarMap) {
261: jarMap.remove(cEntry.fileHandle);
262: }
263: }
264: }
265: }
266: }
267:
268: /**
269: * This Inner Class representing an entry in the Cache
270: */
271: class CacheEntry {
272:
273: private boolean fromJar;
274: private File fileHandle;
275: private long lastModTime;
276:
277: /**
278: * Constructor for an class representing an entry in the Cache
279: * @param isfromJar <CODE>true</CODE> if class is existing in Jarfile
280: * @param fHandle a File handle to the classfile or the Jar file in
281: * which this class is existing
282: */
283: public CacheEntry(boolean isfromJar, File fHandle) {
284: fromJar = isfromJar;
285: fileHandle = fHandle;
286: if (fileHandle != null && fileHandle.canRead()) {
287: lastModTime = fHandle.lastModified();
288: } else {
289: lastModTime = Long.MIN_VALUE;
290: }
291: }
292:
293: }
294:
295: /**
296: * This Inner Class is used to filter the search in a directory.
297: */
298: static class SuffixFilenameFilter implements FilenameFilter {
299: String suffix;
300:
301: SuffixFilenameFilter(String suffix) {
302: this .suffix = suffix;
303: }
304:
305: /**
306: * Implementation of the abstract method accept
307: * @param file File handle
308: * @param name Name
309: * @return <CODE>true</CODE> if accept
310: */
311: public boolean accept(File file, String name) {
312: return name.endsWith(suffix);
313: }
314: }
315:
316: /**
317: * A static method that returns a string representing all the classes and the
318: * Jar files in the Provider class base directory
319: * This method is called by the JSPServletWrapper
320: * @return Provider classpath as a String
321: */
322: public static String getProviderClasspath() {
323:
324: StringBuffer tempPath = new StringBuffer(providerClassBaseDir);
325: // get the list of all jar files in the base search directory
326: SuffixFilenameFilter sff = new SuffixFilenameFilter(".jar");
327: File[] jarFiles = new File(providerClassBaseDir).listFiles(sff);
328: if (jarFiles != null) {
329: for (int i = 0; i < jarFiles.length; i++) {
330: tempPath.append(File.pathSeparatorChar);
331: tempPath.append(jarFiles[i].getPath());
332: }
333: }
334: return tempPath.toString();
335: }
336:
337: }
|