All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer Maven / Gradle / Ivy

There is a newer version: 6.21.2
Show newest version
/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2005 Works, Inc. All rights reserved.
 * http://www.works.com
 * Copyright (C) 2005 - 2016 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.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 .
 */

/*
 * 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.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVirtualizable;
import net.sf.jasperreports.engine.JRVirtualizer;
import net.sf.jasperreports.engine.util.VirtualizationSerializer;

import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Abstract base for LRU and serialization based virtualizer
 *
 * @author John Bindel
 */
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
	{
		private final int maxSize;
		private final ReferenceQueue refQueue;
		private final LinkedHashMap map;

		Cache(int maxSize)
		{
			this.maxSize = maxSize;
			map = new LinkedHashMap(16, 0.75f, true);
			refQueue = new ReferenceQueue();
		}

		protected JRVirtualizable getMapValue(CacheReference val)
		{
			JRVirtualizable o;
			if (val == null)
			{
				o = null;
			}
			else
			{
				if (val.isEnqueued())
				{
					o = null;
				}
				else
				{
					o = val.get();
				}
			}
			return o;
		}

		protected CacheReference 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 boolean contains(String id)
		{
			purge();
			
			return map.containsKey(id);
		}
		
		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 List evictionCandidates()
		{
			if (map.size() <= maxSize)
			{
				return Collections.emptyList();
			}
			
			int candidateCount = map.size() - maxSize;
			List candidates = new ArrayList();
			Iterator> mapIterator = map.entrySet().iterator();
			while (candidates.size() < candidateCount && mapIterator.hasNext())
			{
				Entry entry = mapIterator.next();
				JRVirtualizable value = getMapValue(entry.getValue());
				
				if (value == null)
				{
					// this entry will get removed by purge()
					--candidateCount;
				}
				else if (isEvictable(value))
				{
					if (log.isDebugEnabled())
					{
						log.debug("LRU eviction candidate: " + entry.getKey());
					}
					
					candidates.add(value);
				}
			}

			if (candidates.size() < candidateCount)
			{
				log.debug("The virtualizer is used by more contexts than its in-memory cache size " + maxSize);
			}
			
			return candidates;
		}
		
		public JRVirtualizable remove(String id)
		{
			purge();

			return getMapValue(map.remove(id));
		}

		public Iterator idIterator()
		{
			purge();

			final Iterator valsIt = map.values().iterator();
			return new Iterator()
			{
				@Override
				public boolean hasNext()
				{
					return valsIt.hasNext();
				}

				@Override
				public String next()
				{
					CacheReference ref = valsIt.next();
					return ref.getId();
				}

				@Override
				public void remove()
				{
					valsIt.remove();
				}
			};
		}
	}

	protected final VirtualizationSerializer serializer = new VirtualizationSerializer();
	
	private final Cache pagedIn;

	private final ReferenceMap pagedOut;

	protected volatile WeakReference lastObjectRef;
	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.lastObjectRef = 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 JRVirtualizable lastObject()
	{
		WeakReference ref = lastObjectRef;
		JRVirtualizable object = ref == null ? null : ref.get();
		return object;
	}
	
	protected final void setLastObject(JRVirtualizable o)
	{
		JRVirtualizable currentLast = lastObject();
		if (o != null && currentLast != o)
		{
			// lastObject is mostly an optimization, we don't care if we don't have atomic operations here
			this.lastObjectRef = new WeakReference(o);
			
			synchronized (this)
			{
				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);
				}
			}
		}
	}

	/**
	 * Sets the read only mode for the virtualizer.
	 * 

* When in read-only mode, the virtualizer assumes that virtualizable objects are final * and any change in a virtualizable object's data is discarded. *

* 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(); } @Override public void registerObject(JRVirtualizable o) { if (log.isDebugEnabled()) { log.debug("registering " + o.getUID()); } synchronized (this) { setLastObject(o); JRVirtualizable old = pagedIn.put(o.getUID(), o); if (old != null && old != o) { pagedIn.put(o.getUID(), old); throw new IllegalStateException("Wrong object stored with UID \"" + o.getUID() + "\""); } } if (log.isDebugEnabled()) { log.debug("registered object " + o + " with id " + o.getUID()); } evict(); } protected boolean isEvictable(JRVirtualizable value) { return value.getContext().isDisposed() || !lastObjectSet.containsKey(value); } protected void evict() { //FIXME lucianc also attempt to evict on non-put operations if the last evict was not successful //FIXME lucianc prevent two threads from attempting to evict the same objects List candidates; synchronized (this) { candidates = pagedIn.evictionCandidates(); } for (JRVirtualizable o : candidates) { String uid = o.getUID(); if (o.getContext().tryLock()) { try { boolean evictable; synchronized (this) { // check again due to sequential locking evictable = pagedIn.contains(uid) && isEvictable(o); if (evictable) { pagedIn.remove(uid); } } if (evictable) { if (log.isDebugEnabled()) { log.debug("evicting " + uid); } if (!o.getContext().isDisposed()) { virtualizeData(o); } } else { if (log.isDebugEnabled()) { log.debug("no longer evictable: " + uid); } } } finally { o.getContext().unlock(); } } else { if (log.isDebugEnabled()) { log.debug("couldn't lock for eviction " + uid); } } } } @Override public void deregisterObject(JRVirtualizable o) { String uid = o.getUID(); if (log.isDebugEnabled()) { log.debug("deregistering " + uid); } //try to remove virtual data try { dispose(o); } 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() + "\""); } Object contextLast = lastObjectMap.get(o.getContext()); if (contextLast == o) { lastObjectMap.remove(o.getContext()); lastObjectSet.remove(o); } } 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. } if (log.isDebugEnabled()) { log.debug("deregistered object " + o + " with id " + o.getUID()); } } @Override public void touch(JRVirtualizable o) { // If we just touched this object, don't touch it again. if (lastObject() != o) { //FIXME lucianc this doesn't scale well with concurrency // get the object from the map to update LRU order JRVirtualizable internalObject; synchronized (this) { internalObject = pagedIn.get(o.getUID()); } setLastObject(internalObject); } } @Override public void requestData(JRVirtualizable o) { String uid = o.getUID(); boolean evictRequired = false; o.getContext().lock(); try { if (isPagedOutAndTouch(o, uid)) { if (log.isDebugEnabled()) { log.debug("internalizing " + uid); } // unvirtualize try { pageIn(o); } catch (IOException e) { log.error("Error devirtualizing object", e); throw new JRRuntimeException(e); } synchronized (this) { setLastObject(o); pagedOut.remove(uid); pagedIn.put(uid, o); } o.afterInternalization(); evictRequired = true; } } finally { o.getContext().unlock(); } if (evictRequired) { evict(); } } @Override public void clearData(JRVirtualizable o) { String uid = o.getUID(); if (isPagedOutAndTouch(o, uid)) { // remove virtual data dispose(uid); synchronized (this) { pagedOut.remove(uid); } } } @Override public void virtualizeData(JRVirtualizable o) { String uid = o.getUID(); if (!isPagedOut(uid)) { if (log.isDebugEnabled()) { log.debug("externalizing " + 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); } } } @Override 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 { serializer.writeData(o, out); } 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 { serializer.readData(o, in); } catch (IOException 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 = 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 = 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; protected void dispose(JRVirtualizable o) { dispose(o.getUID()); } /** * Removes the external data associated with a virtualizable object. * * @param virtualId the ID of the virtualizable object */ protected abstract void dispose(String virtualId); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy