001: /*
002: * Copyright (C) 2004 TiongHiang Lee
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * Email: thlee@onemindsoft.org
019: */
020:
021: package org.onemind.jxp;
022:
023: import java.io.*;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import org.onemind.commons.java.datastructure.MruMap;
027: import org.onemind.jxp.parser.*;
028:
029: /**
030: * A caching page source will cache the page source
031: * @author TiongHiang Lee (thlee@onemindsoft.org)
032: *
033: */
034: public abstract class CachingPageSource extends JxpPageSource {
035:
036: /** the cache * */
037: private MruMap _pageCache;
038:
039: /** flag to turn caching on/off * */
040: private boolean _caching = true;
041:
042: /** whether invalidate the cache on parse error * */
043: private boolean _invalidateCacheOnParseError = false;
044:
045: /** the page static variables **/
046: private HashMap _pageStaticVariables = new HashMap();
047:
048: /** the encoding **/
049: private String _encoding;
050:
051: /**
052: * Constructor
053: * (default use 200 cache limit, unlimit timeout)
054: */
055: public CachingPageSource() {
056: this (200);
057: }
058:
059: /**
060: * Constructor
061: * @param cacheSize The default cache size
062: */
063: public CachingPageSource(int cacheSize) {
064: this (cacheSize, 0);
065: }
066:
067: /**
068: * Constructor
069: * @param cacheSize The default cache size
070: * @param timeout time to expire inactive cache
071: */
072: public CachingPageSource(int cacheSize, int timeout) {
073: _pageCache = new MruMap(cacheSize, timeout);
074: }
075:
076: /**
077: * {@inheritDoc}
078: */
079: public final JxpPage getJxpPage(String id)
080: throws JxpPageNotFoundException {
081: JxpPage page = null;
082: if (_caching) {
083: page = (JxpPage) _pageCache.get(id);
084: if (page != null) {
085: return page;
086: }
087: }
088: if (hasStream(id)) {
089: page = new CachedJxpPage(this , id, getEncoding());
090: } else {
091: throw new JxpPageNotFoundException("Page " + id
092: + " not found");
093: }
094: // TODO: synchronize by id so that no two same JxpPage will be loaded
095: if (_caching && page != null) {
096: _pageCache.put(id, page);
097: }
098: return page;
099: }
100:
101: /**
102: * Invalidate the page cache
103: * @param page the page
104: */
105: protected final void invalidatePageCache(JxpPage page) {
106: _pageCache.put(page.getName(), null);
107: }
108:
109: /**
110: * Return whether this is doing caching
111: * @return true if caching
112: */
113: public final boolean isCaching() {
114: return _caching;
115: }
116:
117: /**
118: * Set caching on/off
119: * @param b true if caching on
120: */
121: public final void setCaching(boolean b) {
122: _caching = b;
123: }
124:
125: /**
126: * {@inheritDoc}
127: */
128: public final AstJxpDocument getJxpDocument(JxpPage page)
129: throws JxpPageParseException {
130: CachedJxpPage cachedPage = (CachedJxpPage) page;
131: boolean expired = false;
132: if (cachedPage.getDocument() == null
133: || (expired = isExpired(cachedPage))) {
134: if (expired) {
135: cachedPage.setParseException(null);
136: cachedPage.setDocument(null);
137: purgeStaticVariables(cachedPage);
138: } else {
139: if (cachedPage.hasParseError()) {
140: throw cachedPage.getParseException();
141: }
142: }
143: //need to parse it
144: synchronized (cachedPage) //synchronize
145: {
146: if (cachedPage.getDocument() == null) // no one has parse it since the before synchronize
147: { //check one more time to make sure no previous locker has done it
148: if (cachedPage.hasParseError()) { //still have problem
149: throw cachedPage.getParseException();
150: } //otherwise reparse
151: try {
152: try {
153: InputStream in = loadStream(cachedPage);
154: JxpParser parser = (getEncoding() == null) ? new JxpParser(
155: in)
156: : new JxpParser(
157: new InputStreamReader(in,
158: getEncoding()));
159: AstJxpDocument doc = parser.JxpDocument();
160: cachedPage.setDocument(doc);
161: return doc;
162: } catch (ParseException e) {
163: String message = "Problem parsing page "
164: + page.getName() + ": "
165: + e.getMessage();
166: throw new JxpPageParseException(message, e);
167: } catch (IOException e) {
168: throw new JxpPageParseException(
169: "Problem parsing page "
170: + page.getName() + ": "
171: + e.getMessage(), e);
172: }
173: } catch (JxpPageParseException e) {
174: cachedPage.setParseException(e);
175: //TODO: determine if this is needed
176: if (_invalidateCacheOnParseError) {
177: invalidatePageCache(page);
178: }
179: //rethrow
180: throw e;
181: }
182: }
183: }
184: }
185: return cachedPage.getDocument();
186: }
187:
188: /**
189: * Whether a page is expired
190: * @param page the page
191: * @return true if expired
192: */
193: protected abstract boolean isExpired(CachedJxpPage page);
194:
195: /**
196: * Whether there's input stream from given page name
197: * @param pageName the page name
198: * @return true if has input stream
199: */
200: protected abstract boolean hasStream(String pageName);
201:
202: /**
203: * Load the input stream for the page
204: * @param pageName the page
205: * @return the input stream
206: */
207: protected abstract InputStream loadStream(CachedJxpPage page)
208: throws IOException;
209:
210: /**
211: * Whehter the page identified by id is cached
212: * @param id the id
213: * @return true if cached
214: */
215: public final boolean isJxpPageCached(String id) {
216: return _pageCache.containsKey(id);
217: }
218:
219: /**
220: * Declare a page static variable
221: * @param page the page
222: * @param name the name
223: * @param value the value
224: */
225: /* package */final Object declarePageStaticVariable(JxpPage page,
226: String name, Object value) {
227: String fullName = page.getName() + "." + name;
228: if (_pageStaticVariables.containsKey(fullName)) {
229: throw new IllegalArgumentException("Static variable "
230: + name + " has been declared");
231: } else {
232: return _pageStaticVariables.put(fullName, value);
233: }
234: }
235:
236: /**
237: * Return whether there's a variable declared
238: * @param page the page
239: * @param name the name
240: * @return true if declared
241: */
242: /* package */final boolean hasPageStaticVariable(JxpPage page,
243: String name) {
244: String fullName = page.getName() + "." + name;
245: return _pageStaticVariables.containsKey(fullName);
246: }
247:
248: /**
249: * Get the page static variable
250: * @param page the page
251: * @param name the nama
252: * @return the value
253: */
254: /* package */final Object getPageStaticVariable(JxpPage page,
255: String name) {
256: String fullName = page.getName() + "." + name;
257: return _pageStaticVariables.get(fullName);
258: }
259:
260: /**
261: * {@inheritDoc}
262: */
263: public StringBuffer getErrorSource(JxpPage page, int line, int col)
264: throws IOException {
265: StringBuffer sb = new StringBuffer("Error at page ");
266: sb.append(page.getName());
267: sb.append(" at line ");
268: sb.append(line);
269: sb.append(", column ");
270: sb.append(col);
271: return sb;
272: }
273:
274: /**
275: * @param page
276: * @param name
277: * @param value
278: * @return
279: */
280: /* package */final Object assignPageStaticVariable(
281: CachedJxpPage page, String name, Object value) {
282: String fullName = page.getName() + "." + name;
283: if (!_pageStaticVariables.containsKey(fullName)) {
284: throw new IllegalArgumentException("Static variable "
285: + name + " has not been declared");
286: } else {
287: return _pageStaticVariables.put(fullName, value);
288: }
289: }
290:
291: /**
292: * Purge the static variables for a page
293: * @param page the page
294: */
295: protected final void purgeStaticVariables(CachedJxpPage page) {
296: Iterator it = _pageStaticVariables.keySet().iterator();
297: String prefix = page.getName() + ".";
298: while (it.hasNext()) {
299: String key = (String) it.next();
300: if (key.startsWith(prefix)) {
301: _pageStaticVariables.remove(key);
302: }
303: }
304: }
305:
306: /**
307: * Set the encoding
308: * @param encoding the encoding
309: */
310: public final void setEncoding(String encoding) {
311: _encoding = encoding;
312: }
313:
314: /**
315: * Return the encoding
316: * @return the encoding
317: */
318: public final String getEncoding() {
319: return _encoding;
320: }
321:
322: /**
323: * {@inheritDoc}
324: */
325: public final boolean hasJxpPage(String id) {
326: return ((_caching && _pageCache.containsKey(id)) || hasStream(id));
327: }
328: }
|