001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.cache.xslt;
011:
012: import org.mmbase.cache.Cache;
013: import javax.xml.transform.Templates;
014: import javax.xml.transform.Source;
015: import javax.xml.transform.stream.StreamSource;
016: import org.mmbase.util.ResourceLoader;
017: import org.mmbase.util.ResourceWatcher;
018:
019: import java.util.*;
020: import javax.xml.transform.URIResolver;
021:
022: import org.mmbase.util.logging.Logger;
023: import org.mmbase.util.logging.Logging;
024:
025: /**
026: * A cache for XSL transformation templates. A template can be based
027: * on a file, or on a string. In the first case the cache key is based
028: * on the file name, and the cache entry is invalidated if the file
029: * changes (so, if you uses 'imports' in the XSL template, you have to
030: * touch the file which imports, if the imported files changes). If
031: * the template is based on a string, then the string itself serves as
032: * a key.
033: *
034: * @author Michiel Meeuwissen
035: * @version $Id: TemplateCache.java,v 1.20 2007/08/30 08:05:45 michiel Exp $
036: * @since MMBase-1.6
037: */
038: public class TemplateCache extends Cache<Key, Templates> {
039:
040: private static final Logger log = Logging
041: .getLoggerInstance(TemplateCache.class);
042:
043: private static int cacheSize = 50;
044: private static TemplateCache cache;
045:
046: /**
047: * The Source-s which are based on a file, are added to this FileWatcher, which wil invalidate
048: * the corresponding cache entry when the file changes.
049: */
050: private static ResourceWatcher templateWatcher = new ResourceWatcher(
051: ResourceLoader.getWebRoot()) {
052: public void onChange(String file) {
053: // invalidate cache.
054: if (log.isDebugEnabled())
055: log
056: .debug("Removing " + file.toString()
057: + " from cache");
058: synchronized (cache) {
059: int removed = cache.remove(file);
060: if (removed == 0) {
061: log.error("Could not remove " + file.toString()
062: + " Template(s) from cache!");
063: } else {
064: if (log.isDebugEnabled())
065: log.debug("Removed " + removed
066: + " entries from cache");
067: }
068: }
069: this .remove(file); // should call remove of FileWatcher, not of TemplateCache again.
070: }
071: };
072:
073: /**
074: * Returns the Template cache.
075: */
076: public static TemplateCache getCache() {
077: return cache;
078: }
079:
080: static {
081: cache = new TemplateCache(cacheSize);
082: cache.putCache();
083: templateWatcher.setDelay(10 * 1000); // check every 10 secs if one of the stream source templates was change
084: templateWatcher.start();
085:
086: }
087:
088: public String getName() {
089: return "XSLTemplates";
090: }
091:
092: public String getDescription() {
093: return "XSL Templates";
094: }
095:
096: /**
097: * Creates the XSL Template Cache.
098: */
099: private TemplateCache(int size) {
100: super (size);
101: }
102:
103: /**
104: * Remove all entries associated wit a certain url (used by FileWatcher).
105: *
106: * @param The file under concern
107: * @return The number of cache entries removed
108: */
109:
110: private int remove(String file) {
111: int removed = 0;
112: if (log.isDebugEnabled())
113: log.debug("trying to remove keys containing " + file);
114: Set<Key> remove = new HashSet<Key>();
115: synchronized (this ) {
116: for (Map.Entry<Key, Templates> entry : entrySet()) {
117: Key mapKey = entry.getKey();
118: if (mapKey.getURL().equals(file)) {
119: remove.add(mapKey);
120: }
121: }
122: }
123: for (Key mapKey : remove) {
124: if (remove(mapKey) != null) {
125: removed++;
126: } else {
127: log.warn("Could not remove " + mapKey);
128: }
129: }
130: return removed;
131: }
132:
133: public Templates getTemplates(Source src) {
134: return getTemplates(src, null);
135: }
136:
137: public Templates getTemplates(Source src, URIResolver uri) {
138: Key key = new Key(src, uri);
139: if (log.isDebugEnabled())
140: log.debug("Getting from cache " + key);
141: return get(key);
142: }
143:
144: /**
145: * When removing an entry (because of LRU e.g), then also the FileWatcher must be removed.
146: */
147:
148: public synchronized Templates remove(Key key) {
149: if (log.isDebugEnabled())
150: log.debug("Removing " + key);
151: Templates result = super .remove(key);
152: String url = key.getURL();
153: remove(url);
154: templateWatcher.remove(url);
155: return result;
156: }
157:
158: /**
159: * You can only put Source/Templates values in the cache, so this throws an Exception.
160: *
161: * @throws RuntimeException
162: **/
163:
164: public Templates put(Key key, Templates value) {
165: throw new RuntimeException("wrong types in cache");
166: }
167:
168: public Templates put(Source src, Templates value) {
169: return put(src, value, null);
170: }
171:
172: public Templates put(Source src, Templates value, URIResolver uri) {
173: if (!isActive()) {
174: if (log.isDebugEnabled()) {
175: log.debug("XSLT Cache is not active");
176: }
177: return null;
178: }
179: Key key = new Key(src, uri);
180: Templates res = super .put(key, value);
181: log.service("Put xslt in cache with key " + key);
182: templateWatcher.add(key.getURL());
183: if (log.isDebugEnabled()) {
184: log.debug("have set watch on " + key.getURL());
185: log.trace("currently watching: " + templateWatcher);
186: }
187: return res;
188: }
189:
190: /**
191: * Invocation of the class from the commandline for testing
192: */
193: public static void main(String[] argv) {
194: log.setLevel(org.mmbase.util.logging.Level.DEBUG);
195: try {
196: java.io.File xslFile = java.io.File.createTempFile(
197: "templatecachetest", ".xsl");
198: log.info("using file " + xslFile);
199: java.io.FileWriter fw = new java.io.FileWriter(xslFile);
200: fw
201: .write("<xsl:stylesheet version = \"1.1\" xmlns:xsl =\"http://www.w3.org/1999/XSL/Transform\"></xsl:stylesheet>");
202: fw.close();
203: for (int i = 0; i < 10; i++) {
204: TemplateCache cache = TemplateCache.getCache();
205: Source xsl = new StreamSource(xslFile);
206: org.mmbase.util.xml.URIResolver uri = new org.mmbase.util.xml.URIResolver(
207: xslFile.getParentFile());
208: Templates cachedXslt = cache.getTemplates(xsl, uri);
209: log.info("template cache size " + cache.size()
210: + " entries: " + cache.entrySet());
211: if (cachedXslt == null) {
212: cachedXslt = FactoryCache.getCache()
213: .getFactory(uri).newTemplates(xsl);
214: cache.put(xsl, cachedXslt, uri);
215: } else {
216: if (log.isDebugEnabled())
217: log.debug("Used xslt from cache with "
218: + xsl.getSystemId());
219: }
220: }
221: xslFile.delete();
222: } catch (Exception e) {
223: System.err.println("hmm?" + e);
224: }
225:
226: }
227:
228: }
229:
230: /**
231: * Object to use as a key in the Caches.
232: * Contains the systemid of the XSLT object (if there is one)
233: * and the URIResolver.
234: */
235: class Key {
236: private String src;
237: private URIResolver uri;
238:
239: Key(Source src, URIResolver uri) {
240: this .src = src.getSystemId();
241: this .uri = uri;
242: }
243:
244: public boolean equals(Object o) {
245: if (o instanceof Key) {
246: Key k = (Key) o;
247: return (src == null ? k.src == null : src.equals(k.src))
248: && (uri == null ? k.uri == null : uri.equals(k.uri));
249: }
250: return false;
251: }
252:
253: public int hashCode() {
254: return 32 * (src == null ? 0 : src.hashCode())
255: + (uri == null ? 0 : uri.hashCode());
256: }
257:
258: /**
259: * Returns File object or null
260: */
261: String getURL() {
262: if (src == null)
263: return null;
264: try {
265: return src;
266: } catch (Exception e) {
267: return null;
268: }
269: }
270:
271: public String toString() {
272: return "" + src + "/" + uri;
273: }
274:
275: }
|