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: PresentationLoader.java,v 1.2 2006-06-15 13:44:07 sinisa Exp $
022: */
023:
024: package com.lutris.appserver.server.httpPresentation;
025:
026: //import com.lutris.http.*;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.util.Hashtable;
030: import java.util.StringTokenizer;
031:
032: import com.lutris.logging.LogChannel;
033: import com.lutris.logging.Logger;
034:
035: /**
036: * Loader for presentation objects and resources associated with an
037: * application. It also manages a cache of resources.
038: *
039: * @version $Revision: 1.2 $
040: * @author Mark Diekhans
041: * @since 1.8
042: * @see HttpPresentationManager
043: */
044: class PresentationLoader {
045: /**
046: * Prefix added to the file name portion of the URL. The
047: * resulting names is used to search for classes and
048: * files on the class path.
049: */
050: private final String presentationPrefix;
051:
052: /**
053: * Mime type converter.
054: */
055: private Mime mime;
056:
057: /**
058: * Class loader for the application's classes.
059: */
060: private ClassLoader appClassLoader;
061:
062: /**
063: * Cache for loaded objects. If null, presentation classes are not cached.
064: */
065: private Hashtable presObjCache;
066:
067: /**
068: * Cache for resource presentation objects. If null, resource presentation
069: * classes are not cached.
070: */
071: private Hashtable resourceObjCache;
072:
073: /**
074: * Applications log channel. No logging is done if null.
075: */
076: private LogChannel logChannel;
077:
078: /**
079: * Is DEBUG1 logging enabled?
080: */
081: private boolean debug1LoggingEnabled;
082:
083: /**
084: * Constructor.
085: *
086: * @param appPresentationPrefix Prefix added to the file name portion
087: * of the URL. The resulting names is used to search for classes and
088: * files on the class path.
089: * @param applicationClassLoader The class loader to use for the application.
090: * @param cacheClasses Enables or disables caching of presentation object
091: * classes in memory.
092: * @param cacheFiles Enables or disables caching of files (html, gif, etc)
093: * that are servered as part of the application.
094: * @param presLogChannel Log channel for the presentation. Maybe null.
095: */
096: protected PresentationLoader(String appPresentationPrefix,
097: ClassLoader applicationClassLoader, boolean cacheClasses,
098: boolean cacheFiles, LogChannel presLogChannel)
099: throws HttpPresentationException {
100: logChannel = presLogChannel;
101: if (logChannel != null) {
102: debug1LoggingEnabled = logChannel.isEnabled(Logger.DEBUG1);
103: }
104: appClassLoader = applicationClassLoader;
105: presentationPrefix = cleanUrlPath(appPresentationPrefix);
106: mime = new Mime();
107: if (cacheClasses) {
108: presObjCache = new Hashtable();
109: }
110: if (cacheFiles) {
111: resourceObjCache = new Hashtable();
112: }
113: }
114:
115: /**
116: * Clean up a path name taken from a URL, removing such uglyness
117: * as `./', `//', `//' and `\'. Strips off leading and trailing
118: * `/' and `\'. Uses `/', since this is really a URL, not a file path.
119: *
120: * Includes bug fix submitted by Ben Warren
121: */
122: private String cleanUrlPath(String urlPath)
123: throws HttpPresentationException {
124: // Tokenize on `/' and `\', but drop leading and training spaces,
125: // since they sometimes get dragged in from broken config files.
126:
127: // Do a quick scan through the string to look to look for anything
128: // that is amiss. The idea is to avoid calling the string tokenizer
129: // unless we have to. If there is nothing to do then this quick test
130: // make this routine about 4 times faster.
131: int i = 0;
132: int len = urlPath.length();
133:
134: if (len != 0 && urlPath.charAt(0) == '/') { //FIX - add 'len != 0'test.
135: // This is pretty common and quick to handle here.
136: urlPath = urlPath.substring(1);
137: len--;
138: }
139:
140: if (len != 0) {//FIX - move above block outside this test.
141:
142: // if we start or end with a '/' then we are ugly.
143: // otherwsie scan through the string looking for '\' or "./" or "//"
144: boolean uglyness = (urlPath.charAt(0) == '/' || urlPath
145: .charAt(len - 1) == '/');
146: while (!uglyness && i < len) {
147: char c = urlPath.charAt(i);
148: if (c == '\\') {
149: uglyness = true;
150: } else if (c == '/' && i > 0) {
151: char c0 = urlPath.charAt(i - 1);
152: uglyness = (c0 == '.' || c0 == '/');
153: }
154: i++;
155: }
156:
157: if (!uglyness) {
158: return urlPath;
159: }
160: }
161: // end quick test.
162:
163: // The quick test found a problem...
164:
165: StringTokenizer tokens = new StringTokenizer(urlPath.trim(),
166: "/\\", false);
167: StringBuffer newPath = new StringBuffer();
168:
169: // Loop through path elements, dropping uninteresting parts
170: // of the name.
171: while (tokens.hasMoreTokens()) {
172: String name = tokens.nextToken();
173: if (!(name.equals("/") || name.equals("\\")
174: || name.equals(".") || name.equals(""))) {
175: if (newPath.length() > 0) {
176: newPath.append('/');
177: }
178: newPath.append(name);
179: }
180: }
181: return newPath.toString();
182: }
183:
184: /**
185: * Concert a URL path name to class names. If the path appears invalid, a
186: * bogus class (but reasonable looking) name will be generated which will
187: * lead to an error when loading.
188: */
189: private String convertToClassName(String urlPath) {
190: String className = urlPath;
191: int idx, start, end;
192:
193: // Drop MIME extension
194: idx = className.lastIndexOf('.');
195: if (idx > 0) {
196: className = className.substring(0, idx);
197: }
198:
199: // Convert the URL separator to `.'. Note `\' is not a legal
200: // URL separator
201: className = className.replace('/', '.');
202:
203: // Drop leading or training `.'
204: start = 0;
205: end = className.length() - 1;
206: if (className.charAt(start) == '.') {
207: start++;
208: }
209: if (className.charAt(end) == '.') {
210: end--;
211: }
212: if (start >= end) {
213: // Bad name; put it back so it generates a better error.
214: start = 0;
215: end = className.length() - 1;
216: }
217:
218: return className.substring(start, end + 1);
219: }
220:
221: /**
222: * Load a presentation object by searching the applications class path.
223: *
224: * @param urlPath The path to the presentation object extracted from
225: * the URL. It will converted to a class name.
226: * @exception ClassNotFoundException if this loader or the system loader
227: * can not find or successfully load the class.
228: */
229: private synchronized HttpPresentation loadPresentationObject(
230: String urlPath) throws ClassNotFoundException,
231: IllegalAccessException, InstantiationException {
232: String className = convertToClassName(urlPath);
233:
234: Class classObj = null;
235: if (presObjCache != null) {
236: classObj = (Class) presObjCache.get(className);
237: }
238:
239: if (classObj == null) {
240: // Load class, instantiate object and add to cache.
241: if (appClassLoader != null) {
242: classObj = appClassLoader.loadClass(className);
243: } else {
244: //FIXME: not needed when our own class loader is working.
245: classObj = Class.forName(className);
246: }
247: if (presObjCache != null) {
248: presObjCache.put(className, classObj);
249: }
250: }
251: return (HttpPresentation) classObj.newInstance();
252: }
253:
254: /**
255: * Load create a static file presentation for a standard file.
256: * The class loader is used as find and open the file.
257: */
258: private HttpPresentation loadResourcePresentation(String urlPath,
259: String mimeType) throws ClassNotFoundException,
260: IllegalAccessException, InstantiationException,
261: HttpPresentationException, FilePresentationException,
262: IOException {
263: HttpPresentation resourceObj;
264:
265: if (resourceObjCache != null) {
266: resourceObj = (HttpPresentation) resourceObjCache
267: .get(urlPath);
268: if (resourceObj == null) {
269: resourceObj = new CachedFilePresentation(
270: appClassLoader, urlPath, mimeType);
271: resourceObjCache.put(urlPath, resourceObj);
272: }
273: } else {
274: resourceObj = new CopyFilePresentation(appClassLoader,
275: urlPath, mimeType);
276: }
277: return resourceObj;
278: }
279:
280: /**
281: * Determine if a request URL references a presentation object.
282: *
283: * @param request The request for the presentation.
284: * @return true if the URL has a presentation object MIME type.
285: */
286: protected boolean isPresentationRequest(
287: HttpPresentationRequest request)
288: throws HttpPresentationException {
289: return mime.getType(request.getPresentationObjectPath())
290: .equals("object/presentation");
291: }
292:
293: /**
294: * Load a presentation object or create a presentation object to
295: * deliver a file.
296: */
297: protected HttpPresentation loadPresentation(String urlPath)
298: throws ClassNotFoundException, IllegalAccessException,
299: InstantiationException, HttpPresentationException,
300: FilePresentationException, IOException {
301: HttpPresentation presObj;
302: String presPath;
303: String mimeType = mime.getType(urlPath);
304:
305: try {
306: if (mimeType.equals("object/presentation")) {
307: presPath = presentationPrefix + "/"
308: + cleanUrlPath(urlPath);
309: if (debug1LoggingEnabled) {
310: logChannel.write(Logger.DEBUG1,
311: "loadPresentationObject: " + urlPath);
312: }
313: presObj = loadPresentationObject(presPath);
314: } else {
315: if (debug1LoggingEnabled) {
316: logChannel.write(Logger.DEBUG1,
317: "loadPresentationObject: " + urlPath + " "
318: + mimeType);
319: }
320: /*
321: * If a class is getting loaded by the client; don't use the
322: * presentation prefix. Class must be requested by full
323: * class name.
324: */
325: if (mimeType.equals("application/java-vm")
326: || mimeType.equals("application/java-archive")) {
327: presPath = cleanUrlPath(urlPath);
328: } else {
329: presPath = presentationPrefix + "/"
330: + cleanUrlPath(urlPath);
331: }
332: presObj = loadResourcePresentation(presPath, mimeType);
333: }
334: } catch (ClassNotFoundException except) {
335: if (debug1LoggingEnabled) {
336: logChannel.write(Logger.DEBUG1, " not found: "
337: + urlPath);
338: }
339: throw except;
340: }
341: return presObj;
342: }
343:
344: /**
345: * Determine if the PO cache is enabled.
346: *
347: * @return <code>true</code> is the cache is enabled.
348: */
349: protected boolean isPOCacheEnabled() {
350: return (presObjCache != null);
351: }
352:
353: /**
354: * Return the number of entries in the PO cache.
355: *
356: * @return the number of entries in the cache or 0 is disabled.
357: */
358: protected int sizeofPOCache() {
359: if (presObjCache != null) {
360: return presObjCache.size();
361: } else {
362: return 0;
363: }
364: }
365:
366: /**
367: * Determine if the file resource class cache is enabled.
368: *
369: * @return <code>true</code> is the cache is enabled.
370: */
371: protected boolean isResourceCacheEnabled() {
372: return (resourceObjCache != null);
373: }
374:
375: /**
376: * Return the number of entries in the resource cache.
377: *
378: * @return the number of entries in the cache or 0 is disabled.
379: */
380: protected int sizeofResourceCache() {
381: if (resourceObjCache != null)
382: return resourceObjCache.size();
383: else
384: return 0;
385: }
386:
387: /**
388: * Get a file associated with the application. The file is located
389: * in the same manner as presentation objects are located. That is,
390: * prepending the <CODE>presentationPrefix</CODE> to the specified
391: * path and searching the class path for a directory or JAR containing
392: * the file.
393: *
394: * @param appfileName The file name relative to the
395: * to the application's <CODE>presentationPrefix</CODE>.
396: * @return An input stream associated with the file.
397: * @exception IOException If the file can not be found or opened.
398: */
399: protected InputStream getAppFileAsStream(String appFileName)
400: throws IOException, HttpPresentationException {
401: String path = presentationPrefix + "/"
402: + cleanUrlPath(appFileName);
403: InputStream input = appClassLoader.getResourceAsStream(path);
404: if (input == null) {
405: throw new HttpPresentationException("File \"" + appFileName
406: + "\" not found on application class path");
407: }
408: return input;
409: }
410:
411: /**
412: * Flush the presentation object and resource caches.
413: */
414: protected void flushCache() {
415: if (presObjCache != null) {
416: presObjCache = new Hashtable();
417: }
418: if (resourceObjCache != null) {
419: resourceObjCache = new Hashtable();
420: }
421: }
422:
423: /**
424: * Returns the mime type for the URL path.
425: *
426: * @param urlPath the URL path.
427: */
428: protected String getMimeType(String urlPath) {
429: return mime.getType(urlPath);
430: }
431:
432: /** Add a new mime type to extension mapping. */
433: public void addMimeType(String mimeType, String extension) {
434: mime.addType(mimeType, extension);
435: }
436:
437: /**
438: * Get the application class loader.
439: *
440: * @return The application class loader.
441: */
442: protected ClassLoader getAppClassLoader() {
443: return appClassLoader;
444: }
445: }
|