/*
* Copyright (C) 2004 TiongHiang Lee
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Email: thlee@onemindsoft.org
*/
package org.onemind.jxp;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import org.onemind.commons.java.datastructure.MruMap;
import org.onemind.jxp.parser.*;
/**
* A caching page source will cache the page source
* @author TiongHiang Lee (thlee@onemindsoft.org)
*
*/
public abstract class CachingPageSource extends JxpPageSource
{
/** the cache * */
private MruMap _pageCache;
/** flag to turn caching on/off * */
private boolean _caching = true;
/** whether invalidate the cache on parse error * */
private boolean _invalidateCacheOnParseError = false;
/** the page static variables **/
private HashMap _pageStaticVariables = new HashMap();
/** the encoding **/
private String _encoding;
/**
* Constructor
* (default use 200 cache limit, unlimit timeout)
*/
public CachingPageSource()
{
this(200);
}
/**
* Constructor
* @param cacheSize The default cache size
*/
public CachingPageSource(int cacheSize)
{
this(cacheSize, 0);
}
/**
* Constructor
* @param cacheSize The default cache size
* @param timeout time to expire inactive cache
*/
public CachingPageSource(int cacheSize, int timeout)
{
_pageCache = new MruMap(cacheSize, timeout);
}
/**
* {@inheritDoc}
*/
public final JxpPage getJxpPage(String id) throws JxpPageNotFoundException
{
JxpPage page = null;
if (_caching)
{
page = (JxpPage) _pageCache.get(id);
if (page != null)
{
return page;
}
}
if (hasStream(id))
{
page = new CachedJxpPage(this, id, getEncoding());
} else
{
throw new JxpPageNotFoundException("Page " + id + " not found");
}
// TODO: synchronize by id so that no two same JxpPage will be loaded
if (_caching && page != null)
{
_pageCache.put(id, page);
}
return page;
}
/**
* Invalidate the page cache
* @param page the page
*/
protected final void invalidatePageCache(JxpPage page)
{
_pageCache.put(page.getName(), null);
}
/**
* Return whether this is doing caching
* @return true if caching
*/
public final boolean isCaching()
{
return _caching;
}
/**
* Set caching on/off
* @param b true if caching on
*/
public final void setCaching(boolean b)
{
_caching = b;
}
/**
* {@inheritDoc}
*/
public final AstJxpDocument getJxpDocument(JxpPage page) throws JxpPageParseException
{
CachedJxpPage cachedPage = (CachedJxpPage) page;
boolean expired = false;
if (cachedPage.getDocument() == null || (expired = isExpired(cachedPage)))
{
if (expired)
{
cachedPage.setParseException(null);
cachedPage.setDocument(null);
purgeStaticVariables(cachedPage);
} else
{
if (cachedPage.hasParseError())
{
throw cachedPage.getParseException();
}
}
//need to parse it
synchronized (cachedPage) //synchronize
{
if (cachedPage.getDocument() == null) // no one has parse it since the before synchronize
{ //check one more time to make sure no previous locker has done it
if (cachedPage.hasParseError())
{ //still have problem
throw cachedPage.getParseException();
} //otherwise reparse
try
{
try
{
InputStream in = loadStream(cachedPage);
JxpParser parser = (getEncoding() == null) ? new JxpParser(in) : new JxpParser(new InputStreamReader(
in, getEncoding()));
AstJxpDocument doc = parser.JxpDocument();
cachedPage.setDocument(doc);
return doc;
} catch (ParseException e)
{
String message = "Problem parsing page " + page.getName() + ": " + e.getMessage();
throw new JxpPageParseException(message, e);
} catch (IOException e)
{
throw new JxpPageParseException("Problem parsing page " + page.getName() + ": " + e.getMessage(), e);
}
} catch (JxpPageParseException e)
{
cachedPage.setParseException(e);
//TODO: determine if this is needed
if (_invalidateCacheOnParseError)
{
invalidatePageCache(page);
}
//rethrow
throw e;
}
}
}
}
return cachedPage.getDocument();
}
/**
* Whether a page is expired
* @param page the page
* @return true if expired
*/
protected abstract boolean isExpired(CachedJxpPage page);
/**
* Whether there's input stream from given page name
* @param pageName the page name
* @return true if has input stream
*/
protected abstract boolean hasStream(String pageName);
/**
* Load the input stream for the page
* @param pageName the page
* @return the input stream
*/
protected abstract InputStream loadStream(CachedJxpPage page) throws IOException;
/**
* Whehter the page identified by id is cached
* @param id the id
* @return true if cached
*/
public final boolean isJxpPageCached(String id)
{
return _pageCache.containsKey(id);
}
/**
* Declare a page static variable
* @param page the page
* @param name the name
* @param value the value
*/
/* package */final Object declarePageStaticVariable(JxpPage page, String name, Object value)
{
String fullName = page.getName() + "." + name;
if (_pageStaticVariables.containsKey(fullName))
{
throw new IllegalArgumentException("Static variable " + name + " has been declared");
} else
{
return _pageStaticVariables.put(fullName, value);
}
}
/**
* Return whether there's a variable declared
* @param page the page
* @param name the name
* @return true if declared
*/
/* package */final boolean hasPageStaticVariable(JxpPage page, String name)
{
String fullName = page.getName() + "." + name;
return _pageStaticVariables.containsKey(fullName);
}
/**
* Get the page static variable
* @param page the page
* @param name the nama
* @return the value
*/
/* package */final Object getPageStaticVariable(JxpPage page, String name)
{
String fullName = page.getName() + "." + name;
return _pageStaticVariables.get(fullName);
}
/**
* {@inheritDoc}
*/
public StringBuffer getErrorSource(JxpPage page, int line, int col) throws IOException
{
StringBuffer sb = new StringBuffer("Error at page ");
sb.append(page.getName());
sb.append(" at line ");
sb.append(line);
sb.append(", column ");
sb.append(col);
return sb;
}
/**
* @param page
* @param name
* @param value
* @return
*/
/* package */final Object assignPageStaticVariable(CachedJxpPage page, String name, Object value)
{
String fullName = page.getName() + "." + name;
if (!_pageStaticVariables.containsKey(fullName))
{
throw new IllegalArgumentException("Static variable " + name + " has not been declared");
} else
{
return _pageStaticVariables.put(fullName, value);
}
}
/**
* Purge the static variables for a page
* @param page the page
*/
protected final void purgeStaticVariables(CachedJxpPage page)
{
Iterator it = _pageStaticVariables.keySet().iterator();
String prefix = page.getName() + ".";
while (it.hasNext())
{
String key = (String) it.next();
if (key.startsWith(prefix))
{
_pageStaticVariables.remove(key);
}
}
}
/**
* Set the encoding
* @param encoding the encoding
*/
public final void setEncoding(String encoding)
{
_encoding = encoding;
}
/**
* Return the encoding
* @return the encoding
*/
public final String getEncoding()
{
return _encoding;
}
/**
* {@inheritDoc}
*/
public final boolean hasJxpPage(String id)
{
return ((_caching && _pageCache.containsKey(id)) || hasStream(id));
}
}