net.sf.jasperreports.engine.fill.JRVirtualizationContext.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.fill.JRVirtualizationContext.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 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 <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.engine.fill;

import java.io.IOException;
import java.io.ObjectInputStream.GetField;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
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.PrintElementVisitor;
import net.sf.jasperreports.engine.base.JRVirtualPrintPage;
import net.sf.jasperreports.engine.base.VirtualElementsData;
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 (lucianc@users.sourceforge.net)
 */
public class JRVirtualizationContext implements Serializable, VirtualizationListener<VirtualElementsData> {
    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<JasperPrint, JRVirtualizationContext> contexts = new ReferenceMap<JasperPrint, JRVirtualizationContext>(
            ReferenceMap.ReferenceStrength.WEAK, ReferenceMap.ReferenceStrength.WEAK);

    private transient JRVirtualizationContext parentContext;
    private transient JRVirtualizer virtualizer;
    private transient JasperReportsContext jasperReportsContext;

    private Map<String, Renderable> cachedRenderers;
    private Map<String, JRTemplateElement> cachedTemplates;

    private volatile boolean readOnly;
    private volatile boolean disposed;

    private int pageElementSize;

    private transient List<VirtualizationListener<VirtualElementsData>> listeners;

    private transient volatile PrintElementVisitor<Void> cacheTemplateVisitor;

    private transient ReentrantLock lock;

    /**
     * Constructs a context.
     */
    public JRVirtualizationContext(JasperReportsContext jasperReportsContext) {
        this.jasperReportsContext = jasperReportsContext;

        cachedRenderers = new ConcurrentHashMap<String, Renderable>(16, 0.75f, 1);
        cachedTemplates = new ConcurrentHashMap<String, JRTemplateElement>(16, 0.75f, 1);

        pageElementSize = JRPropertiesUtil.getInstance(jasperReportsContext)
                .getIntegerProperty(JRVirtualPrintPage.PROPERTY_VIRTUAL_PAGE_ELEMENT_SIZE, 0);

        initLock();
    }

    protected JRVirtualizationContext(JRVirtualizationContext parentContext) {
        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.pageElementSize = parentContext.pageElementSize;

        // always locking the master context
        this.lock = parentContext.lock;
    }

    private void initLock() {
        lock = new ReentrantLock(true);
    }

    /**
     * Adds a virtualization listener.
     * 
     * @param listener
     */
    public void addListener(VirtualizationListener<VirtualElementsData> listener) {
        if (listeners == null) {
            listeners = new CopyOnWriteArrayList<VirtualizationListener<VirtualElementsData>>();
        }

        listeners.add(listener);
    }

    /**
     * Remove a virtualization listener.
     * 
     * @param listener
     */
    public void removeListener(VirtualizationListener<VirtualElementsData> listener) {
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    @Override
    public void beforeExternalization(JRVirtualizable<VirtualElementsData> object) {
        if (listeners != null) {
            for (VirtualizationListener<VirtualElementsData> listener : listeners) {
                listener.beforeExternalization(object);
            }
        }
    }

    public void afterExternalization(JRVirtualizable<VirtualElementsData> object) {
    }

    @Override
    public void afterInternalization(JRVirtualizable<VirtualElementsData> object) {
        if (listeners != null) {
            for (VirtualizationListener<VirtualElementsData> 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 <code>true</code> 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 <code>true</code> 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.
     * <p>
     * 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.
     * <p>
     * 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 <code>null</code> 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();
        cachedRenderers = (Map<String, Renderable>) fields.get("cachedRenderers", null);
        cachedTemplates = (Map<String, JRTemplateElement>) fields.get("cachedTemplates", 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();
    }

    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<Void> visitor,
            Collection<? extends JRPrintElement> elements) {
        DeepPrintElementVisitor<Void> deepVisitor = new DeepPrintElementVisitor<Void>(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<Void> {
        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);
            }
        }
        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;
        }
        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;
    }

    public JRVirtualizationContext getMasterContext() {
        JRVirtualizationContext context = this;
        while (context.parentContext != null) {
            context = context.parentContext;
        }
        return context;
    }

    public Map<String, Renderable> getCachedRenderers() {
        return cachedRenderers;
    }

    public Map<String, JRTemplateElement> getCachedTemplates() {
        return cachedTemplates;
    }
}