/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2005 - 2009 Works, Inc. All rights reserved.
* http://www.works.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports 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 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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 JasperReports. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Licensed to Jaspersoft Corporation under a Contributer Agreement
*/
package net.sf.jasperreports.engine.fill;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.jasperreports.engine.JRConstants;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVirtualizable;
import net.sf.jasperreports.engine.JRVirtualizer;
import org.apache.commons.collections.LRUMap;
import org.apache.commons.collections.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstract base for LRU and serialization based virtualizer
*
* @author John Bindel
* @version $Id: JRAbstractLRUVirtualizer.java 3717 2010-04-09 10:01:33Z teodord $
*/
public abstract class JRAbstractLRUVirtualizer implements JRVirtualizer
{
private static final Log log = LogFactory.getLog(JRAbstractLRUVirtualizer.class);
protected static class CacheReference extends WeakReference
{
private final String id;
public CacheReference(JRVirtualizable o, ReferenceQueue queue)
{
super(o, queue);
id = o.getUID();
}
public String getId()
{
return id;
}
}
/**
* This class keeps track of how many objects are currently in memory, and
* when there are too many, it pushes the last touched one to disk.
*/
protected class Cache
{
protected class LRUScanMap extends LRUMap
{
private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
public LRUScanMap(int maxSize)
{
super(maxSize);
}
protected void removeLRU()
{
Map.Entry entry = getFirst();
boolean found = isRemovable(entry);
if (!found)
{
Iterator entriesIt = entrySet().iterator();
entriesIt.next(); //skipping the first, which is already checked
while(!found && entriesIt.hasNext())
{
entry = (Entry) entriesIt.next();
found = isRemovable(entry);
}
}
if (!found)
{
throw new JRRuntimeException("The virtualizer is used by more contexts than its in-memory cache size " + getMaximumSize());
}
Object key = entry.getKey();
Object value = entry.getValue();
this.remove(key);
processRemovedLRU(key,value);
}
protected boolean isRemovable(Map.Entry entry)
{
JRVirtualizable value = getMapValue(entry.getValue());
return value == null || !lastObjectSet.containsKey(value);
}
protected void processRemovedLRU(Object key, Object value)
{
JRVirtualizable o = getMapValue(value);
if (o != null)
{
virtualizeData(o);
}
}
}
private final ReferenceQueue refQueue;
private final LRUScanMap map;
Cache(int maxSize)
{
map = new LRUScanMap(maxSize);
refQueue = new ReferenceQueue();
}
protected JRVirtualizable getMapValue(Object val)
{
JRVirtualizable o;
if (val == null)
{
o = null;
}
else
{
Reference ref = (Reference) val;
if (ref.isEnqueued())
{
o = null;
}
else
{
o = (JRVirtualizable) ref.get();
}
}
return o;
}
protected Object toMapValue(JRVirtualizable val)
{
return val == null ? null : new CacheReference(val, refQueue);
}
protected void purge()
{
CacheReference ref;
while ((ref = (CacheReference) refQueue.poll()) != null)
{
map.remove(ref.getId());
}
}
public JRVirtualizable get(String id)
{
purge();
return getMapValue(map.get(id));
}
public JRVirtualizable put(String id, JRVirtualizable o)
{
purge();
return getMapValue(map.put(id, toMapValue(o)));
}
public JRVirtualizable remove(String id)
{
purge();
return getMapValue(map.remove(id));
}
public Iterator idIterator()
{
purge();
final Iterator valsIt = map.values().iterator();
return new Iterator()
{
public boolean hasNext()
{
return valsIt.hasNext();
}
public Object next()
{
CacheReference ref = (CacheReference) valsIt.next();
return ref.getId();
}
public void remove()
{
valsIt.remove();
}
};
}
}
protected static final int CLASSLOADER_IDX_NOT_SET = -1;
protected static boolean isAncestorClassLoader(ClassLoader loader)
{
for (
ClassLoader ancestor = JRAbstractLRUVirtualizer.class.getClassLoader();
ancestor != null;
ancestor = ancestor.getParent())
{
if (ancestor.equals(loader))
{
return true;
}
}
return false;
}
protected final Map classLoadersIndexes = new HashMap();
protected final List classLoadersList = new ArrayList();
protected class ClassLoaderAnnotationObjectOutputStream extends ObjectOutputStream
{
public ClassLoaderAnnotationObjectOutputStream(OutputStream out) throws IOException
{
super(out);
}
protected void annotateClass(Class clazz) throws IOException
{
super.annotateClass(clazz);
ClassLoader classLoader = clazz.getClassLoader();
int loaderIdx;
if (clazz.isPrimitive()
|| classLoader == null
|| isAncestorClassLoader(classLoader))
{
loaderIdx = CLASSLOADER_IDX_NOT_SET;
}
else
{
Integer idx = (Integer) classLoadersIndexes.get(classLoader);
if (idx == null)
{
idx = Integer.valueOf(classLoadersList.size());
classLoadersIndexes.put(classLoader, idx);
classLoadersList.add(classLoader);
}
loaderIdx = idx.intValue();
}
writeShort(loaderIdx);
}
}
protected class ClassLoaderAnnotationObjectInputStream extends ObjectInputStream
{
public ClassLoaderAnnotationObjectInputStream(InputStream in) throws IOException
{
super(in);
}
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException
{
Class clazz;
try
{
clazz = super.resolveClass(desc);
readShort();
}
catch (ClassNotFoundException e)
{
int loaderIdx = readShort();
if (loaderIdx == CLASSLOADER_IDX_NOT_SET)
{
throw e;
}
ClassLoader loader = (ClassLoader) classLoadersList.get(loaderIdx);
clazz = Class.forName(desc.getName(), false, loader);
}
return clazz;
}
}
private final Cache pagedIn;
private final ReferenceMap pagedOut;
protected JRVirtualizable lastObject;
protected ReferenceMap lastObjectMap;
protected ReferenceMap lastObjectSet;
private boolean readOnly;
/**
* @param maxSize
* the maximum size (in JRVirtualizable objects) of the paged in
* cache.
*/
protected JRAbstractLRUVirtualizer(int maxSize)
{
this.pagedIn = new Cache(maxSize);
this.pagedOut = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
this.lastObject = null;
this.lastObjectMap = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);
this.lastObjectSet = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD);
}
protected synchronized final boolean isPagedOut(String id)
{
return pagedOut.containsKey(id);
}
protected synchronized boolean isPagedOutAndTouch(JRVirtualizable o, String uid)
{
boolean virtualized = isPagedOut(uid);
if (!virtualized)
{
touch(o);
}
return virtualized;
}
protected final void setLastObject(JRVirtualizable o)
{
if (lastObject != o)
{
if (o != null)
{
JRVirtualizationContext context = o.getContext();
Object ownerLast = lastObjectMap.get(context);
if (ownerLast != o)
{
if (ownerLast != null)
{
lastObjectSet.remove(ownerLast);
}
lastObjectMap.put(context, o);
lastObjectSet.put(o, Boolean.TRUE);
}
}
this.lastObject = o;
}
}
/**
* Sets the read only mode for the virtualizer.
* <p/>
* When in read-only mode, the virtualizer assumes that virtualizable objects are final
* and any change in a virtualizable object's data is discarded.
* <p/>
* When the virtualizer is used for multiple virtualization contexts (in shared mode),
* calling this method would override the read-only flags from all the contexts and all the
* objects will be manipulated in read-only mode.
* Use {@link JRVirtualizationContext#setReadOnly(boolean) JRVirtualizationContext.setReadOnly(boolean)}
* to set the read-only mode for one specific context.
*
* @param ro the read-only mode to set
*/
public void setReadOnly(boolean ro)
{
this.readOnly = ro;
}
/**
* Determines whether the virtualizer is in read-only mode.
*
* @return whether the virtualizer is in read-only mode
* @see #setReadOnly(boolean)
*/
public boolean isReadOnly()
{
return readOnly;
}
protected final boolean isReadOnly(JRVirtualizable o)
{
return readOnly || o.getContext().isReadOnly();
}
public synchronized void registerObject(JRVirtualizable o)
{
setLastObject(o);
JRVirtualizable old = pagedIn.put(o.getUID(), o);
if (old != null)
{
pagedIn.put(o.getUID(), old);
throw new IllegalStateException("Wrong object stored with UID \"" + o.getUID() + "\"");
}
}
public void deregisterObject(JRVirtualizable o)
{
String uid = o.getUID();
//try to remove virtual data
try
{
dispose(o.getUID());
}
catch (Exception e)
{
log.error("Error removing virtual data", e);
//ignore
}
synchronized(this)
{
JRVirtualizable oldIn = pagedIn.remove(uid);
if (oldIn != null)
{
if (oldIn != o)
{
pagedIn.put(uid, oldIn);
throw new IllegalStateException("Wrong object stored with UID \"" + o.getUID() + "\"");
}
}
else
{
Object oldOut = pagedOut.remove(uid);
if (oldOut != null && oldOut != o)
{
pagedOut.put(uid, oldOut);
throw new IllegalStateException("Wrong object stored with UID \"" + o.getUID() + "\"");
}
}
// We don't really care if someone deregisters an object
// that's not registered.
}
}
public synchronized void touch(JRVirtualizable o)
{
// If we just touched this object, don't touch it again.
if (this.lastObject != o)
{
setLastObject(pagedIn.get(o.getUID()));
}
}
public void requestData(JRVirtualizable o)
{
String uid = o.getUID();
if (isPagedOutAndTouch(o, uid))
{
// unvirtualize
try
{
pageIn(o);
}
catch (IOException e)
{
log.error("Error devirtualizing object", e);
throw new JRRuntimeException(e);
}
o.afterInternalization();
synchronized (this)
{
setLastObject(o);
pagedOut.remove(uid);
pagedIn.put(uid, o);
}
}
}
public void clearData(JRVirtualizable o)
{
String uid = o.getUID();
if (isPagedOutAndTouch(o, uid))
{
// remove virtual data
dispose(uid);
synchronized (this)
{
pagedOut.remove(uid);
}
}
}
public void virtualizeData(JRVirtualizable o)
{
String uid = o.getUID();
if (!isPagedOut(uid))
{
o.beforeExternalization();
// virtualize
try
{
pageOut(o);
}
catch (IOException e)
{
log.error("Error virtualizing object", e);
throw new JRRuntimeException(e);
}
o.afterExternalization();
// Wait until we know it worked before tossing the data.
o.removeVirtualData();
synchronized (this)
{
pagedOut.put(uid, o);
}
}
}
protected void finalize() throws Throwable //NOSONAR
{
cleanup();
super.finalize();
}
/**
* Writes serialized indentity and virtual data of a virtualizable object to a stream.
*
* @param o the serialized object
* @param out the output stream
* @throws JRRuntimeException
*/
protected final void writeData(JRVirtualizable o, OutputStream out)
throws JRRuntimeException
{
try
{
ObjectOutputStream oos = new ClassLoaderAnnotationObjectOutputStream(out);
oos.writeObject(o.getIdentityData());
oos.writeObject(o.getVirtualData());
oos.flush();
}
catch (IOException e)
{
log.error("Error virtualizing object", e);
throw new JRRuntimeException(e);
}
}
/**
* Reads serialized identity and virtual data for a virtualizable object
* from a stream.
*
* @param o the virtualizable object
* @param in the input stream
* @throws JRRuntimeException
*/
protected final void readData(JRVirtualizable o, InputStream in)
throws JRRuntimeException
{
try
{
ObjectInputStream ois = new ClassLoaderAnnotationObjectInputStream(in);
o.setIdentityData(ois.readObject());
o.setVirtualData(ois.readObject());
}
catch (IOException e)
{
log.error("Error devirtualizing object", e);
throw new JRRuntimeException(e);
}
catch (ClassNotFoundException e)
{
log.error("Error devirtualizing object", e);
throw new JRRuntimeException(e);
}
}
protected synchronized void reset()
{
readOnly = false;
}
protected final void disposeAll()
{
// Remove all paged-out swap files.
for (Iterator it = pagedOut.keySet().iterator(); it.hasNext();)
{
String id = (String) it.next();
try
{
dispose(id);
it.remove();
}
catch (Exception e)
{
log.error("Error cleaning up virtualizer.", e);
// Do nothing because we want to try to remove all swap files.
}
}
for (Iterator it = pagedIn.idIterator(); it.hasNext();)
{
String id = (String) it.next();
try
{
dispose(id);
it.remove();
}
catch (Exception e)
{
log.error("Error cleaning up virtualizer.", e);
// Do nothing because we want to try to remove all swap files.
}
}
}
/**
* Writes a virtualizable object's data to an external storage.
*
* @param o a virtualizable object
* @throws IOException
*/
protected abstract void pageOut(JRVirtualizable o) throws IOException;
/**
* Reads a virtualizable object's data from an external storage.
*
* @param o a virtualizable object
* @throws IOException
*/
protected abstract void pageIn(JRVirtualizable o) throws IOException;
/**
* Removes the external data associated with a virtualizable object.
*
* @param virtualId the ID of the virtualizable object
*/
protected abstract void dispose(String virtualId);
}