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

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

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2022 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 .
 */
package net.sf.jasperreports.engine.fill;

import java.awt.Font;
import java.io.IOException;
import java.io.ObjectInputStream.GetField;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;

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

import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRConstants;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVirtualizable;
import net.sf.jasperreports.engine.JRVirtualizationHelper;
import net.sf.jasperreports.engine.JRVirtualizer;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.PrintElementId;
import net.sf.jasperreports.engine.PrintElementVisitor;
import net.sf.jasperreports.engine.base.JRVirtualPrintPage;
import net.sf.jasperreports.engine.base.VirtualElementsData;
import net.sf.jasperreports.engine.base.VirtualizableElementList;
import net.sf.jasperreports.engine.fonts.FontUtil;
import net.sf.jasperreports.engine.util.DeepPrintElementVisitor;
import net.sf.jasperreports.engine.util.UniformPrintElementVisitor;
import net.sf.jasperreports.renderers.Renderable;

/**
 * Context used to store data shared by virtualized objects resulted from a report fill process.
 * 
 * @author Lucian Chirita ([email protected])
 */
public class JRVirtualizationContext implements Serializable, VirtualizationListener
{
	private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
	
	public static final String EXCEPTION_MESSAGE_KEY_LOCKING_INTERRUPTED = "fill.virtualizer.locking.interrupted";
	public static final String EXCEPTION_MESSAGE_KEY_RENDERER_NOT_FOUND_IN_CONTEXT = "fill.virtualizer.renderer.not.found.in.context";
	public static final String EXCEPTION_MESSAGE_KEY_TEMPLATE_NOT_FOUND_IN_CONTEXT = "fill.virtualizer.template.not.found.in.context";
	
	private static final Log log = LogFactory.getLog(JRVirtualizationContext.class);
	
	private static final ReferenceMap contexts = 
		new ReferenceMap<>(
			ReferenceMap.ReferenceStrength.WEAK, ReferenceMap.ReferenceStrength.WEAK
			);

	private UUID id;
	private boolean root;
	private transient JRVirtualizationContext parentContext;
	private transient JRVirtualizer virtualizer;
	private transient JasperReportsContext jasperReportsContext;
	
	private Map cachedRenderers;
	private Map cachedTemplates;
	
	private Map subContexts;
	
	private transient Set frameContexts;
	private transient Map virtualizableLists;
	
	private volatile boolean readOnly;
	private volatile boolean disposed;
	
	private int pageElementSize;
	
	private transient List> listeners;
	
	private transient volatile PrintElementVisitor cacheTemplateVisitor;
	
	private transient ReentrantLock lock;
	
	/**
	 * Constructs a context.
	 */
	public JRVirtualizationContext(JasperReportsContext jasperReportsContext)
	{
		this.id = UUID.randomUUID();
		this.root = true;
		this.jasperReportsContext = jasperReportsContext;
		
		cachedRenderers = new ConcurrentHashMap<>(16, 0.75f, 1);
		cachedTemplates = new ConcurrentHashMap<>(16, 0.75f, 1);
		virtualizableLists = new ConcurrentHashMap<>(16, 0.75f, 1);
		
		subContexts = new ConcurrentHashMap<>(16, 0.75f, 1);
		subContexts.put(this.id, this);
		
		pageElementSize = JRPropertiesUtil.getInstance(jasperReportsContext).getIntegerProperty(JRVirtualPrintPage.PROPERTY_VIRTUAL_PAGE_ELEMENT_SIZE, 0);
		
		initLock();
		
		if (log.isDebugEnabled())
		{
			log.debug("created " + id);
		}
	}

	protected JRVirtualizationContext(JRVirtualizationContext parentContext)
	{
		this.id = UUID.randomUUID();
		this.root = false;
		this.parentContext = parentContext;
		this.virtualizer = parentContext.virtualizer;
		this.jasperReportsContext = parentContext.jasperReportsContext;

		// using the same caches as the parent
		this.cachedRenderers = parentContext.cachedRenderers;
		this.cachedTemplates = parentContext.cachedTemplates;
		this.virtualizableLists = parentContext.virtualizableLists;
		
		this.subContexts = new ConcurrentHashMap<>(16, 0.75f, 1);
		this.subContexts.put(this.id, this);

		this.pageElementSize = parentContext.pageElementSize;
		
		// always locking the master context
		this.lock = parentContext.lock;
		
		if (log.isDebugEnabled())
		{
			log.debug("created sub context " + id + ", parent " + parentContext.id);
		}
	}
	
	private void initLock()
	{
		lock = new ReentrantLock(true);
	}
	
	/**
	 * Adds a virtualization listener.
	 * 
	 * @param listener
	 */
	public void addListener(VirtualizationListener listener)
	{
		if (listeners == null)
		{
			listeners = new CopyOnWriteArrayList<>();
		}
		
		listeners.add(listener);
		
		if (frameContexts != null)
		{
			for (JRVirtualizationContext frameContext : frameContexts)
			{
				frameContext.addListener(listener);
			}
		}
	}
	
	/**
	 * Remove a virtualization listener.
	 * 
	 * @param listener
	 */
	public void removeListener(VirtualizationListener listener)
	{
		if (listeners != null)
		{
			listeners.remove(listener);
		}
		
		if (frameContexts != null)
		{
			for (JRVirtualizationContext frameContext : frameContexts)
			{
				frameContext.removeListener(listener);
			}
		}
	}

	@Override
	public void beforeExternalization(JRVirtualizable object)
	{
		if (listeners != null)
		{
			for (VirtualizationListener listener : listeners)
			{
				listener.beforeExternalization(object);	
			}
		}
	}
	
	public void afterExternalization(JRVirtualizable object)
	{
	}
	
	@Override
	public void afterInternalization(JRVirtualizable object)
	{
		if (listeners != null)
		{
			for (VirtualizationListener listener : listeners)
			{
				listener.afterInternalization(object);
			}
		}
	}
	
	/**
	 * Caches an image renderer.
	 * 
	 * @param renderer the image whose renderer should be cached
	 */
	public void cacheRenderer(Renderable renderer)
	{
		if (renderer != null)
		{
			cachedRenderers.put(renderer.getId(), renderer);
		}
	}

	
	/**
	 * Retrieves a cached image renderer based on an ID.
	 * 
	 * @param id the ID
	 * @return the cached image renderer for the ID
	 */
	public Renderable getCachedRenderer(String id)
	{
		return cachedRenderers.get(id);
	}

	
	/**
	 * Determines whether a cached image renderer for a specified ID exists.
	 * 
	 * @param id the ID
	 * @return true if and only if the context contains a cached renderer with the specified ID
	 */
	public boolean hasCachedRenderer(String id)
	{
		return cachedRenderers.containsKey(id);
	}

	
	/**
	 * Determines whether a cached {@link JRTemplateElement template} with a specified ID exists.
	 * 
	 * @param id the template ID
	 * @return true if and only if the context contains a cached template with the specified ID
	 */
	public boolean hasCachedTemplate(String id)
	{
		return cachedTemplates.containsKey(id);
	}
	
	
	/**
	 * Caches an element template.
	 * 
	 * @param template the template to cache
	 */
	public void cacheTemplate(JRTemplateElement template)
	{
		Object old = cachedTemplates.put(template.getId(), template);
		if (old == null && log.isDebugEnabled())
		{
			log.debug("Cached template " + template + " having id " + template.getId());
		}
	}
	
	
	/**
	 * Retrieves a cached template.
	 * 
	 * @param templateId the template ID
	 * @return the cached template having the given ID
	 */
	public JRTemplateElement getCachedTemplate(String templateId)
	{
		return cachedTemplates.get(templateId);
	}

	/**
	 * Caches the template of an element.
	 * 
	 * @param element the element whose template to cache 
	 */
	public void cacheTemplate(JRPrintElement element)
	{
		if (cacheTemplateVisitor == null)
		{
			cacheTemplateVisitor = new CacheTemplateVisitor();
		}
		
		element.accept(cacheTemplateVisitor, null);
	}

	/**
	 * Determines whether this context has been marked as read-only.
	 * 
	 * @return whether this context has been marked as read-only
	 * @see #setReadOnly(boolean)
	 */
	public boolean isReadOnly()
	{
		return readOnly;
	}


	/**
	 * Sets the read-only flag for this context.
	 * 

* When in read-only mode, all the virtualizable objects belonging to this context * are assumed final by the virtualizer and any change in a virtualizable object's data * would be discarded on virtualization. * * @param readOnly the read-only flag */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } /** * Registers a virtualization context for {@link JasperPrint JasperPrint} object. * * @param context the virtualization context * @param print the print object */ public static void register(JRVirtualizationContext context, JasperPrint print) { synchronized (contexts) { contexts.put(print, context); } } /** * Returns the virtualization context registered for a print object. *

* When the engine fills a report using a virtualizer, it {@link #register(JRVirtualizationContext, JasperPrint) registers} * the virtualization context with the generated {@link JasperPrint JasperPrint} object so that the caller * would be able to retrieve the context based on the returned print object. * * @param print a print object * @return the virtualization context registered for the print object, or null if no context * has been registered */ public static JRVirtualizationContext getRegistered(JasperPrint print) { synchronized (contexts) { return contexts.get(print); } } /** * Returns the virtual page size used by the report. * * @return the virtual page size used by the report * @see JRVirtualPrintPage#PROPERTY_VIRTUAL_PAGE_ELEMENT_SIZE */ public int getPageElementSize() { return pageElementSize; } /** * Set the virtual page size used by the report. * * @param pageElementSize the virtual page size * @see JRVirtualPrintPage#PROPERTY_VIRTUAL_PAGE_ELEMENT_SIZE */ public void setPageElementSize(int pageElementSize) { this.pageElementSize = pageElementSize; } /** * Returns the virtualizer used by this context. */ public JRVirtualizer getVirtualizer() { return virtualizer; } public void setVirtualizer(JRVirtualizer virtualizer) { this.virtualizer = virtualizer; } @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { setThreadJasperReportsContext(); GetField fields = in.readFields(); root = fields.get("root", false); cachedRenderers = (Map) fields.get("cachedRenderers", null); cachedTemplates = (Map) fields.get("cachedTemplates", null); subContexts = (Map) fields.get("subContexts", null); readOnly = fields.get("readOnly", false); // use configured default if serialized by old version pageElementSize = fields.get("pageElementSize", JRPropertiesUtil.getInstance(jasperReportsContext).getIntegerProperty( JRVirtualPrintPage.PROPERTY_VIRTUAL_PAGE_ELEMENT_SIZE, 0)); setThreadVirtualizer(); initLock(); if (root) { virtualizableLists = new ConcurrentHashMap<>(16, 0.75f, 1); subContexts.values().stream().forEach(context -> {context.virtualizableLists = virtualizableLists;}); } } private void setThreadVirtualizer() { JRVirtualizer threadVirtualizer = JRVirtualizationHelper.getThreadVirtualizer(); if (threadVirtualizer != null) { virtualizer = threadVirtualizer; } } private void setThreadJasperReportsContext() { JasperReportsContext threadJasperReportsContext = JRVirtualizationHelper.getThreadJasperReportsContext(); if (threadJasperReportsContext != null) { jasperReportsContext = threadJasperReportsContext; } else if (jasperReportsContext == null) { if (log.isDebugEnabled()) { log.debug("no thread JRContext, using default"); } jasperReportsContext = DefaultJasperReportsContext.getInstance(); } } /** * Traverses all the elements on the page, including the ones placed inside * {@link JRPrintFrame frames}. * * @param visitor element visitor */ protected void traverseDeepElements(PrintElementVisitor visitor, Collection elements) { DeepPrintElementVisitor deepVisitor = new DeepPrintElementVisitor<>(visitor); for (JRPrintElement element : elements) { element.accept(deepVisitor, null); } } /** * Print element visitor that caches print element templates. * * @see JRVirtualizationContext#cacheTemplate(JRPrintElement) */ class CacheTemplateVisitor extends UniformPrintElementVisitor { public CacheTemplateVisitor() { super(true); } @Override protected void visitElement(JRPrintElement element, Void arg) { if (element instanceof JRTemplatePrintElement) { JRTemplatePrintElement templateElement = (JRTemplatePrintElement) element; JRTemplateElement template = templateElement.getTemplate(); if (template != null) { cacheTemplate(template); } } } } public Object replaceSerializedObject(Object obj) { Object replace = obj; if (obj instanceof JRTemplateElement) { JRTemplateElement template = (JRTemplateElement) obj; String templateId = template.getId(); if (hasCachedTemplate(templateId)) { replace = new JRVirtualPrintPage.JRIdHolderTemplateElement(templateId); } else { if (log.isDebugEnabled()) { log.debug("Template " + template + " having id " + template.getId() + " not found in virtualization context cache"); } } } else if (obj instanceof Renderable) { Renderable renderer = (Renderable) obj; if (hasCachedRenderer(renderer.getId())) { replace = new JRVirtualPrintPage.JRIdHolderRenderer(renderer); } } else if (obj instanceof JRVirtualizationContext) { JRVirtualizationContext context = (JRVirtualizationContext) obj; if (subContexts.containsKey(context.id)) { replace = new VirtualizationContextIdHolder(context.id); } else if (log.isDebugEnabled()) { log.debug("Context " + context.id + " not found under " + id); } } return replace; } public Object resolveSerializedObject(Object obj) { Object resolve = obj; if (obj instanceof JRVirtualPrintPage.JRIdHolderTemplateElement) { JRVirtualPrintPage.JRIdHolderTemplateElement template = (JRVirtualPrintPage.JRIdHolderTemplateElement) obj; JRTemplateElement cachedTemplate = getCachedTemplate(template.getId()); if (cachedTemplate == null) { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_TEMPLATE_NOT_FOUND_IN_CONTEXT, new Object[]{template.getId()}); } resolve = cachedTemplate; } else if (obj instanceof JRVirtualPrintPage.JRIdHolderRenderer) { JRVirtualPrintPage.JRIdHolderRenderer renderer = (JRVirtualPrintPage.JRIdHolderRenderer) obj; Renderable cachedRenderer = getCachedRenderer(renderer.getId()); if (cachedRenderer == null) { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_RENDERER_NOT_FOUND_IN_CONTEXT, new Object[]{renderer.getId()}); } resolve = cachedRenderer; } else if (obj instanceof VirtualizationContextIdHolder) { UUID id = ((VirtualizationContextIdHolder) obj).getId(); JRVirtualizationContext context = subContexts.get(id); if (context == null) { throw new JRRuntimeException("Context with ID " + id + " not found"); } resolve = context; } else if (obj instanceof Font) { resolve = FontUtil.getInstance(jasperReportsContext).resolveDeserializedFont((Font) obj); } return resolve; } /** * Acquires a lock on this context. */ public void lock() { try { lock.lockInterruptibly(); } catch (InterruptedException e) { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_LOCKING_INTERRUPTED, (Object[])null, e); } } /** * Attempts to acquire a lock on this context. * * @return true iff the lock was acquired on the context */ public boolean tryLock() { return lock.tryLock(); } /** * Releases the lock previously acquired on this context. */ public void unlock() { lock.unlock(); } /** * Marks this context as disposed in order to instruct the virtualizer * that pages owned by this context are no longer used. */ public void dispose() { disposed = true; } /** * Determines if this context is marked as disposed. * * @return whether this context has been marked as disposed */ public boolean isDisposed() { return disposed || (parentContext != null && parentContext.isDisposed()); } public JRVirtualizationContext getMasterContext() { JRVirtualizationContext context = this; while (context.parentContext != null) { context = context.parentContext; } return context; } public Map getCachedRenderers() { return cachedRenderers; } public Map getCachedTemplates() { return cachedTemplates; } public JRVirtualizationContext getFramesContext() { if (frameContexts == null) { frameContexts = new HashSet<>(); } JRVirtualizationContext frameContext; if (frameContexts.isEmpty()) { frameContext = new JRVirtualizationContext(this); frameContext.subContexts = subContexts; subContexts.put(frameContext.id, frameContext); if (listeners != null) { for (VirtualizationListener listener : listeners) { frameContext.addListener(listener); } } if (log.isDebugEnabled()) { log.debug(id + " created frames context " + frameContext.id); } frameContexts.add(frameContext); } else { frameContext = frameContexts.iterator().next(); } return frameContext; } public Map getVirtualizableLists() { return virtualizableLists; } public void cacheVirtualizableList(PrintElementId id, VirtualizableElementList virtualizableList) { virtualizableLists.put(id, virtualizableList); } public VirtualizableElementList getVirtualizableList(PrintElementId id) { return virtualizableLists.get(id); } public void updateParent(JRVirtualizationContext destinationContext) { destinationContext.subContexts.put(id, this); if (destinationContext.listeners != null) { for (VirtualizationListener listener : destinationContext.listeners) { if (listeners == null || !listeners.contains(listener))//TODO keep a set? { addListener(listener); } } } } public JasperReportsContext getJasperReportsContext() { return jasperReportsContext; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy