/* * 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); }