001: /*
002: * Copyright 2001 Sun Microsystems, Inc. All rights reserved.
003: * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
004: */
005: package com.sun.portal.desktop.context;
006:
007: import java.io.File;
008: import java.io.BufferedReader;
009: import java.io.FileNotFoundException;
010: import java.io.IOException;
011: import java.io.UnsupportedEncodingException;
012: import java.io.FileInputStream;
013: import java.util.Map;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.Properties;
017:
018: import com.sun.portal.util.UnicodeInputStreamReader;
019:
020: import com.sun.portal.desktop.util.FileLookup;
021: import com.sun.portal.desktop.template.ParsedTagArray;
022:
023: public class FileTemplateContext implements TemplateContext {
024: //
025: // FileTemplateContext uses a two-level caching scheme. Both caches reference
026: // TemplateCacheElement or PropertyCacheElement objects as values. The key
027: // for the "ByKey" cache is a key that is derived only from the information
028: // that is based in to the getTemplate or getTemplateProperties call, and
029: // not based on actually looking up the file on disk. The "ByFile" cache is
030: // based on a file that is actually on disk.
031: //
032: // For each template file on disk, there is exactly one entry in the "ByFile"
033: // cache. But since multiple keys (based on the arguments to getTemplate) may
034: // map to the same file, the same TemplateCacheElement may show up more than once
035: // in the "ByKey" cache.
036: //
037: // The size of the "ByFile" cache is limited by the number of template files
038: // actually on disk. Entries are never removed from the cache.
039: // The size of the "ByKey" cache is limited by the number of key combinations
040: // that can be generated based on the various channels, desktop types, locales, etc.
041: // that exist in the system. This is potentially large. Entries are not
042: // removed from the cache.
043: //
044: private Map templatesByKey = Collections
045: .synchronizedMap(new HashMap());
046: private Map templatesByFile = Collections
047: .synchronizedMap(new HashMap());
048: private int scanInterval = 0;
049:
050: //
051: // FileTemplateContext uses a cache for non-existing template files in
052: // getTemplate and getTemplatePath.
053: // The cache maps templateKey to a TemplateCacheElement. An entry
054: // in the cache means that the template as represented by the templateKey
055: // has been searched for in either getTemplate and/or getTemplatePath and the
056: // template file was not found. This entry will cause any subsequent getTemplate
057: // and/or getTemplatePath calls searching for the same templateKey to assume that
058: // the template does not exist for the duration of the cache entry's template scan
059: // interval. This reduces expensive calls to FileLookup.
060: //
061: // The size of the cache is limited by the same sizing factors affecting the
062: // templateByKey cache. The cache entries are removed when the scan
063: // interval expires when checks are made as a result of clients calling getTemplate
064: // and/or getTemplatePath.
065: //
066: private Map nonexistTemplateCacheByKey = Collections
067: .synchronizedMap(new HashMap());
068:
069: public FileTemplateContext() {
070: // nothing
071: }
072:
073: /*
074: private static void log(String msg) {
075: if (DesktopAppContextThreadLocalizer.exists()) {
076: DesktopAppContext dac = DesktopAppContextThreadLocalizer.get();
077: dac.debugError(msg);
078: }
079: }
080: */
081: public synchronized void init(int templateScanInterval) {
082: //
083: // templates could possibly be more efficient as a TreeMap.
084: //
085:
086: //
087: // TBD(jtb): there is a slight conceptual problem here. a context object
088: // is user/session specific. here, we're using it to get global
089: // per-jvm data (the template base dir).
090: //
091: // the impl of the context api must get the template base dir from a
092: // global location.
093: //
094: // we are assigning the context to a debug context because we aren't
095: // allowed to use it for anything other than global stuff (we can't
096: // use the per-user aspects.
097: //
098: scanInterval = templateScanInterval;
099: }
100:
101: public ParsedTagArray getTemplate(String base, String type,
102: String locale, String app, String provider,
103: String clientFilePath, String file) {
104: ParsedTagArray template = null;
105: template = get(base, type, locale, app, provider,
106: clientFilePath, file);
107:
108: return template;
109: }
110:
111: public Properties getTemplateProperties(String base, String type,
112: String locale, String app, String provider,
113: String clientFilePath, String file) {
114: Properties templateProperties = null;
115: templateProperties = getProperties(base, type, locale, app,
116: provider, clientFilePath, file);
117:
118: return templateProperties;
119: }
120:
121: public File getTemplatePath(String base, String type,
122: String locale, String app, String provider,
123: String clientFilePath, String fileName) {
124: File file = getCachedFile(base, type, locale, app, provider,
125: clientFilePath, fileName);
126: return file;
127: }
128:
129: public File getTemplateMostSpecificPath(String base, String type,
130: String locale, String app, String provider,
131: String clientFilePath, String fileName) {
132: File file = FileLookup.getMostSpecific(base, type, locale, app,
133: provider, clientFilePath, fileName);
134: return file;
135: }
136:
137: private String errorString(String base, String type, String locale,
138: String channel, String provider, String clientPath,
139: String file, String msg) {
140: String error = msg + ": " + "base=" + base + ", type=" + type
141: + ", locale=" + locale + ", channel=" + channel
142: + ", provider=" + provider + ", clientPath="
143: + clientPath + ", file=" + file;
144: return error;
145: }
146:
147: /**
148: * Get a template.
149: * If the template exists in the cache, then it is returned.
150: * If not, then an attempt is made to read the template from
151: * disk.
152: *
153: * If the exact template is cannot be found under the locale
154: * specified, a locale-indpendent directory is checked. If the
155: * template cannot be found in either of these places, the get is
156: * repeated, but with type=default. The ordering of the directories
157: * that are checked is as follows:
158: *
159: * <type>_<locale>/<component>
160: * <type>_<locale>
161: * <type>/<component>
162: * <type>
163: * default_<locale>/<component>
164: * default_<locale>
165: * default/<component>
166: * default
167: *
168: */
169: public ParsedTagArray get(String base, String type, String locale,
170: String component, String provider, String filename) {
171: return get(base, type, locale, component, provider, null,
172: filename);
173: }
174:
175: /**
176: * Get a template.
177: * If the template exists in the cache, then it is returned.
178: * If not, then an attempt is made to read the template from
179: * disk.
180: *
181: * If the exact template is cannot be found under the locale
182: * specified, a locale-indpendent directory is checked. If the
183: * template cannot be found in either of these places, the get is
184: * repeated, but with type=default. The ordering of the directories
185: * that are checked is as follows:
186: *
187: * <type>_<locale>/<component>/<clientPath>
188: * <type>_<locale>/<component>
189: * <type>_<locale>/<clientPath>
190: * <type>_<locale>
191: * <type>/<component>/<clientPath>
192: * <type>/<component>
193: * <type>
194: * default_<locale>/<component>/<clientPath>
195: * default_<locale>/<component>
196: * default_<locale>
197: * default/<component>/<clientPath>
198: * default/<component>
199: * default/<clientPath>
200: * default
201: *
202: */
203: public ParsedTagArray get(String base, String type, String locale,
204: String component, String provider, String clientPath,
205: String filename) {
206: TemplateKey tkey = new TemplateKey(base, type, locale,
207: component, provider, clientPath, filename);
208: TemplateCacheElement template = null;
209: Object o = templatesByKey.get(tkey);
210: if ((o != null) && (o instanceof TemplateCacheElement)) {
211: template = (TemplateCacheElement) o;
212: }
213: if (template == null || template.expired()) {
214: // Check non-existing cache for an entry for this template. If an entry is found and its scan interval has not expired, skip
215: // the FileLookup call. This prevents searching for a template before the non-existing cache entry for the template expires.
216: if (!nonexistCacheCheck(tkey)) {
217: File tk = FileLookup.getFirstExisting(base, type,
218: locale, component, provider, clientPath,
219: filename);
220: if (tk == null) {
221: // Populate non-existing cache so that this template will not be searched again before the scan interval expires.
222: populateNonexistCache(tkey);
223: throw new ContextError(errorString(base, type,
224: locale, component, provider, clientPath,
225: filename,
226: "templatesByKey.get(): template not found"));
227: }
228: template = getTemplate(tk);
229: if (template == null) {
230: throw new ContextError(errorString(base, type,
231: locale, component, provider, clientPath,
232: filename, "getTemplate(): returns null"));
233: } else if (template.expired()) {
234: // reset the cache expiration time when the file has not changed but cache expired
235: template.resetExpireTime();
236: }
237: templatesByKey.put(tkey, template);
238: } else {
239: // throw exception to maintain same semantics when template is not found
240: throw new ContextError(errorString(base, type, locale,
241: component, provider, clientPath, filename,
242: "templatesByKey.get(): template not found"));
243: }
244: } else {
245: // read in the template when only the file object is cached
246: if ((template.getFile() != null)
247: && (template.getData() == null)) {
248: template = getTemplate(template.getFile());
249: if (template == null) {
250: throw new ContextError(errorString(base, type,
251: locale, component, provider, clientPath,
252: filename, "getTemplate(): returns null"));
253: } else if (template.expired()) {
254: // reset the cache expiration time when the file has not changed but cache expired
255: template.resetExpireTime();
256: }
257: templatesByKey.put(tkey, template);
258: }
259: }
260: return template.getData();
261: }
262:
263: /**
264: * Get message.properties file
265: * If the message.properties exists in the cache, then it is returned.
266: * If not, then an attempt is made to read the message.properties from
267: * disk.
268: *
269: * If the exact message.properties cannot be found under the locale
270: * specified, a locale-indpendent directory is checked. If the
271: * message.properties cannot be found in either of these places, the get is
272: * repeated, but with type=default. The ordering of the directories
273: * that are checked is as follows:
274: *
275: * <type>_<locale>/<component>/<clientPath>
276: * <type>_<locale>/<component>
277: * <type>_<locale>/<clientPath>
278: * <type>_<locale>
279: * <type>/<component>/<clientPath>
280: * <type>/<component>
281: * <type>
282: * default_<locale>/<component>/<clientPath>
283: * default_<locale>/<component>
284: * default_<locale>
285: * default/<component>/<clientPath>
286: * default/<component>
287: * default/<clientPath>
288: * default
289: *
290: */
291:
292: public Properties getProperties(String base, String type,
293: String locale, String component, String provider,
294: String clientPath, String filename) {
295: TemplateKey pkey = new TemplateKey(base, type, locale,
296: component, provider, clientPath, filename);
297: PropertyCacheElement msgProperties = null;
298: Object o = templatesByKey.get(pkey);
299: if ((o != null) && (o instanceof PropertyCacheElement)) {
300: msgProperties = (PropertyCacheElement) o;
301: }
302: if (msgProperties == null || msgProperties.expired()) {
303: // Check non-existing cache for an entry for this message.properties file. If an entry is found and its scan interval has not expired, skip
304: // the FileLookup call. This prevents searching for a file before the non-existing cache entry for the message.properties expires.
305: if (!nonexistCacheCheck(pkey)) {
306: File tk = FileLookup.getFirstExisting(base, type,
307: locale, component, provider, clientPath,
308: filename);
309: if (tk == null) {
310: // Populate non-existing cache so that this template will not be searched again before the scan interval expires.
311: populateNonexistCache(pkey);
312: throw new ContextError(
313: errorString(base, type, locale, component,
314: provider, clientPath, filename,
315: "templatesByKey.get(): message.properties not found"));
316: }
317: msgProperties = getPropertiesFile(tk);
318: if (msgProperties == null) {
319: throw new ContextError(errorString(base, type,
320: locale, component, provider, clientPath,
321: filename,
322: "getPropertiesFile(): returns null"));
323: } else if (msgProperties.expired()) {
324: // reset the cache expiration time when the file has not changed but cache expired
325: msgProperties.resetExpireTime();
326: }
327: templatesByKey.put(pkey, msgProperties);
328: } else {
329: // throw exception to maintain same semantics when message.properties is not found
330: throw new ContextError(
331: errorString(base, type, locale, component,
332: provider, clientPath, filename,
333: "templatesByKey.get(): message.properties not found"));
334: }
335: } else {
336: // read in the message.properties when only the file object is cached
337: if ((msgProperties.getFile() != null)
338: && (msgProperties.getData() == null)) {
339: msgProperties = getPropertiesFile(msgProperties
340: .getFile());
341: if (msgProperties == null) {
342: throw new ContextError(errorString(base, type,
343: locale, component, provider, clientPath,
344: filename,
345: "getPropertiesFile(): returns null"));
346: } else if (msgProperties.expired()) {
347: // reset the cache expiration time when the file has not changed but cache expired
348: msgProperties.resetExpireTime();
349: }
350: templatesByKey.put(pkey, msgProperties);
351: }
352: }
353: return msgProperties.getData();
354: }
355:
356: public File getCachedFile(String base, String type, String locale,
357: String component, String provider, String clientPath,
358: String filename) {
359: File result = null;
360: TemplateKey tkey = new TemplateKey(base, type, locale,
361: component, provider, clientPath, filename);
362: TemplateCacheElement template = null;
363: Object o = templatesByKey.get(tkey);
364: if ((o != null) && (o instanceof TemplateCacheElement)) {
365: template = (TemplateCacheElement) o;
366: }
367: if (template == null || template.expired()) {
368: // Check non-existing cache for an entry for this template. If an entry is found and its scan interval has not expired, skip
369: // the FileLookup call. This prevents searching for a template before the non-existing cache entry for the template expires.
370: if (!nonexistCacheCheck(tkey)) {
371: result = FileLookup.getFirstExisting(base, type,
372: locale, component, provider, clientPath,
373: filename);
374: if (result != null) {
375: template = new TemplateCacheElement(result, result
376: .lastModified(), scanInterval);
377: templatesByKey.put(tkey, template);
378: } else {
379: // Populate non-existing cache so that this template will not be searched again before the scan interval expires.
380: populateNonexistCache(tkey);
381: }
382: }
383: } else {
384: // File is cached but still have to make sure it exists now. If the cached file does not exist at this time, need to
385: // call FileLookup to search for a new file. If the cached file still exists, return the cached file object. This
386: // prevents searching for a more specific file before the scan interval of the cached entry expires.
387: result = template.getFile();
388: if ((result != null) && (!result.exists())) {
389: File tk = FileLookup.getFirstExisting(base, type,
390: locale, component, provider, clientPath,
391: filename);
392: if (tk != null) {
393: template = new TemplateCacheElement(tk, tk
394: .lastModified(), scanInterval);
395: templatesByKey.put(tkey, template);
396: } else {
397: // Populate non-existing cache so that this template will not be searched again before the scan interval expires.
398: populateNonexistCache(tkey);
399: // Remove the old template from the key cache
400: templatesByKey.remove(tkey);
401: }
402: // Remove the old template file from the file cache
403: templatesByFile.remove(result);
404: result = tk;
405: }
406: }
407: return result;
408: }
409:
410: private TemplateCacheElement getTemplate(File tk) {
411: //
412: // attempt to get template from the cache.
413: //
414: TemplateCacheElement template = getCachedTemplate(tk);
415:
416: if (template == null || fileHasChanged(template, tk)) {
417: synchronized (this ) {
418: template = getCachedTemplate(tk);
419:
420: if (template == null || fileHasChanged(template, tk)) {
421: template = readTemplate(tk);
422: if (template != null) {
423: templatesByFile.put(tk, template);
424: }
425: }
426: }
427: }
428:
429: //
430: // might be null here
431: //
432: return template;
433: }
434:
435: private PropertyCacheElement getPropertiesFile(File tk) {
436: //
437: // attempt to get message.properties file from the cache.
438: //
439: PropertyCacheElement msgProperties = getCachedProperties(tk);
440:
441: if (msgProperties == null || fileHasChanged(msgProperties, tk)) {
442: synchronized (this ) {
443: msgProperties = getCachedProperties(tk);
444:
445: if (msgProperties == null
446: || fileHasChanged(msgProperties, tk)) {
447: msgProperties = readProperties(tk);
448: if (msgProperties != null) {
449: templatesByFile.put(tk, msgProperties);
450: }
451: }
452: }
453: }
454:
455: //
456: // might be null here
457: //
458: return msgProperties;
459: }
460:
461: private boolean fileHasChanged(TemplateCacheElement template,
462: File tk) {
463: boolean fileHasChanged = false;
464:
465: //
466: // check modification time of cached element against that of the
467: // disk file
468: //
469: long cacheModified = template.getLastModified();
470: long fileModified = tk.lastModified();
471:
472: if ((fileModified == 0) || (fileModified > cacheModified)) {
473: //
474: // == 0 should never happen. this means that the
475: // file does not exist, but we already called
476: // getFirstExisting() to see if it existed ...
477: //
478: // in any case, if it gets removed then we'll note
479: // it as "changed" and try to read it, which
480: // will give us an error at that point
481: //
482: fileHasChanged = true;
483: }
484:
485: return fileHasChanged;
486: }
487:
488: private boolean fileHasChanged(PropertyCacheElement msgProperties,
489: File tk) {
490: boolean fileHasChanged = false;
491:
492: //
493: // check modification time of cached element against that of the
494: // disk file
495: //
496: long cacheModified = msgProperties.getLastModified();
497: long fileModified = tk.lastModified();
498:
499: if ((fileModified == 0) || (fileModified > cacheModified)) {
500: //
501: // == 0 should never happen. this means that the
502: // file does not exist, but we already called
503: // getFirstExisting() to see if it existed ...
504: //
505: // in any case, if it gets removed then we'll note
506: // it as "changed" and try to read it, which
507: // will give us an error at that point
508: //
509: fileHasChanged = true;
510: }
511:
512: return fileHasChanged;
513: }
514:
515: private TemplateCacheElement readTemplate(File tk) {
516: BufferedReader in = null;
517: try {
518: in = new BufferedReader(new UnicodeInputStreamReader(
519: new FileInputStream(tk)));
520: } catch (FileNotFoundException fnfe) {
521: return null;
522: } catch (UnsupportedEncodingException uee) {
523: return null;
524: }
525:
526: long lastModified = tk.lastModified();
527: StringBuffer data = new StringBuffer(256);
528:
529: TemplateCacheElement template = null;
530:
531: try {
532: String curLine = null;
533: // loop through the file reading in a line at a time
534: while ((curLine = in.readLine()) != null) {
535: data.append(curLine).append("\n");
536: }
537:
538: ParsedTagArray pta = new ParsedTagArray(data);
539: template = new TemplateCacheElement(tk, lastModified,
540: scanInterval);
541: template.setData(pta);
542: } catch (IOException e) {
543: template = null;
544: } finally {
545: if (in != null) {
546: try {
547: in.close();
548: } catch (IOException ioe) {
549: template = null;
550: }
551: }
552: }
553:
554: return template;
555: }
556:
557: private PropertyCacheElement readProperties(File tk) {
558: Properties p = null;
559: FileInputStream in = null;
560: long lastModified = 0;
561: PropertyCacheElement msgProperties = null;
562:
563: try {
564: in = new FileInputStream(tk);
565: lastModified = tk.lastModified();
566: p = new Properties();
567: p.load(in);
568: msgProperties = new PropertyCacheElement(tk, lastModified,
569: scanInterval);
570: msgProperties.setData(p);
571:
572: } catch (FileNotFoundException fnfe) {
573: msgProperties = null;
574: } catch (IOException e) {
575: msgProperties = null;
576: } finally {
577: if (in != null) {
578: try {
579: in.close();
580: } catch (IOException ioe) {
581: msgProperties = null;
582: }
583: }
584: }
585: return msgProperties;
586: }
587:
588: private TemplateCacheElement getCachedTemplate(File tk) {
589:
590: TemplateCacheElement template = null;
591:
592: Object o = templatesByFile.get(tk);
593: if ((o != null) && (o instanceof TemplateCacheElement)) {
594: template = (TemplateCacheElement) o;
595: }
596:
597: //
598: // at this point, template may be null
599: //
600: return template;
601: }
602:
603: private PropertyCacheElement getCachedProperties(File tk) {
604:
605: PropertyCacheElement msgProperties = null;
606: Object o = templatesByFile.get(tk);
607: if ((o != null) && (o instanceof PropertyCacheElement)) {
608: msgProperties = (PropertyCacheElement) o;
609: }
610:
611: //
612: // at this point, msgProperties may be null
613: //
614: return msgProperties;
615: }
616:
617: private boolean nonexistCacheCheck(TemplateKey tkey) {
618:
619: boolean result = false;
620:
621: TemplateCacheElement cacheEntry = (TemplateCacheElement) nonexistTemplateCacheByKey
622: .get(tkey);
623: if (cacheEntry != null) {
624: if (cacheEntry.expired()) {
625: nonexistTemplateCacheByKey.remove(tkey);
626: } else if ((cacheEntry.getFile() == null)
627: && (cacheEntry.getLastModified() == -1)) {
628: result = true;
629: }
630: }
631: return result;
632: }
633:
634: private void populateNonexistCache(TemplateKey tkey) {
635: TemplateCacheElement cacheEntry = new TemplateCacheElement(
636: null, -1, scanInterval);
637: nonexistTemplateCacheByKey.put(tkey, cacheEntry);
638: }
639:
640: private class TemplateKey {
641: String base;
642: String type;
643: String locale;
644: String component;
645: String provider;
646: String clientPath;
647: String filename;
648: String hashKey;
649: int hashCode = -1;
650:
651: TemplateKey(String base, String type, String locale,
652: String component, String provider, String clientPath,
653: String filename) {
654: this .base = (base == null) ? "" : base;
655: this .type = (type == null) ? "" : type;
656: this .locale = (locale == null) ? "" : locale;
657: this .component = (component == null) ? "" : component;
658: this .provider = (provider == null) ? "" : provider;
659: this .clientPath = (clientPath == null) ? "" : clientPath;
660: this .filename = (filename == null) ? "" : filename;
661: hashKey = new StringBuffer(base).append(type)
662: .append(locale).append(component).append(provider)
663: .append(clientPath).append(filename).toString();
664: }
665:
666: public boolean equals(Object obj) {
667: if (obj == null || !(obj instanceof TemplateKey)) {
668: return false;
669: }
670: TemplateKey tk = (TemplateKey) obj;
671: return base.equals(tk.base) && type.equals(tk.type)
672: && locale.equals(tk.locale)
673: && component.equals(tk.component)
674: && provider.equals(tk.provider)
675: && clientPath.equals(tk.clientPath)
676: && filename.equals(tk.filename);
677: }
678:
679: public int hashCode() {
680: if (hashCode == -1) {
681: hashCode = hashKey.hashCode();
682: }
683:
684: return hashCode;
685: }
686: }
687:
688: private class TemplateCacheElement {
689:
690: private long expireTime = 0;
691: private long lastModified = 0;
692: private ParsedTagArray data = null;
693: private int scanInterval = 0;
694: private File file = null;
695:
696: TemplateCacheElement(File f, long last, int scanInterval) {
697: file = f;
698: lastModified = last;
699: this .scanInterval = scanInterval * 1000;
700: resetExpireTime();
701: }
702:
703: long getLastModified() {
704: return lastModified;
705: }
706:
707: void setLastModified(long last) {
708: lastModified = last;
709: }
710:
711: ParsedTagArray getData() {
712: return data;
713: }
714:
715: File getFile() {
716: return file;
717: }
718:
719: void setData(ParsedTagArray d) {
720: data = d;
721: resetExpireTime();
722: }
723:
724: boolean expired() {
725: return System.currentTimeMillis() > expireTime;
726: }
727:
728: void resetExpireTime() {
729: expireTime = System.currentTimeMillis() + scanInterval;
730: }
731:
732: }
733:
734: /* The English messages in templates can be put in a property
735: * file message.properties. This file will be located in the same
736: * directory as the template file. The PropertyCacheElement represents the
737: * message.properties file being cached */
738:
739: private class PropertyCacheElement {
740:
741: private long expireTime = 0;
742: private long lastModified = 0;
743: private Properties data = null;
744: private int scanInterval = 0;
745: private File file = null;
746:
747: PropertyCacheElement(File f, long last, int scanInterval) {
748: file = f;
749: lastModified = last;
750: this .scanInterval = scanInterval * 1000;
751: resetExpireTime();
752: }
753:
754: long getLastModified() {
755: return lastModified;
756: }
757:
758: void setLastModified(long last) {
759: lastModified = last;
760: }
761:
762: Properties getData() {
763: return data;
764: }
765:
766: File getFile() {
767: return file;
768: }
769:
770: void setData(Properties d) {
771: data = d;
772: resetExpireTime();
773: }
774:
775: boolean expired() {
776: return System.currentTimeMillis() > expireTime;
777: }
778:
779: void resetExpireTime() {
780: expireTime = System.currentTimeMillis() + scanInterval;
781: }
782:
783: }
784:
785: }
|