Java tutorial
/* * 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.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRPrintPage; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.JRVirtualizable; import net.sf.jasperreports.engine.JasperPrint; 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.VirtualizablePageElements; import net.sf.jasperreports.engine.type.EvaluationTimeEnum; import net.sf.jasperreports.engine.util.LinkedMap; import net.sf.jasperreports.engine.util.UniformPrintElementVisitor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author Lucian Chirita (lucianc@users.sourceforge.net) */ public class DelayedFillActions implements VirtualizationListener<VirtualElementsData> { private static final Log log = LogFactory.getLog(DelayedFillActions.class); protected static final String FILL_CACHE_KEY_ID = DelayedFillActions.class.getName() + "#id"; public static final String EXCEPTION_MESSAGE_KEY_ELEMENT_NOT_FOUND = "fill.delayed.fill.actions.element.not.found"; private final int id; private final BaseReportFiller reportFiller; private final JRFillContext fillContext; // we can use HashMap because the map is initialized in the beginning and doesn't change afterwards private final HashMap<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> actionsMap; private Map<Integer, JRFillElement> fillElements; private Set<Integer> masterFillElementIds; private Set<JRVirtualizationContext> listenedContexts; private Set<Integer> transferredIds; public DelayedFillActions(BaseReportFiller reportFiller) { this.id = assignId(reportFiller); this.reportFiller = reportFiller; this.fillContext = reportFiller.fillContext; this.actionsMap = new HashMap<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>>(); this.fillElements = new HashMap<Integer, JRFillElement>(); this.masterFillElementIds = new HashSet<Integer>(); this.listenedContexts = new HashSet<JRVirtualizationContext>(); } private static int assignId(BaseReportFiller reportFiller) { AtomicInteger counter = (AtomicInteger) reportFiller.fillContext.getFillCache(FILL_CACHE_KEY_ID); if (counter == null) { // we just need a mutable integer, there's no actual concurrency here counter = new AtomicInteger(); reportFiller.fillContext.setFillCache(FILL_CACHE_KEY_ID, counter); } return counter.incrementAndGet(); } public int getId() { return id; } public void createDelayedEvaluationTime(JREvaluationTime evaluationTime) { LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> evaluationActions = new LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>(); actionsMap.put(evaluationTime, evaluationActions); } protected void registerPage(JRPrintPage page) { if (page instanceof JRVirtualPrintPage) { JRVirtualizationContext virtualizationContext = ((JRVirtualPrintPage) page).getVirtualizationContext(); if (!listenedContexts.contains(virtualizationContext)) { //FIXMEBOOK part reports use a single context which will collect all listeners virtualizationContext.addListener(this); listenedContexts.add(virtualizationContext); if (log.isDebugEnabled()) { log.debug(id + " registered virtualization listener on " + virtualizationContext); } } } } public void dispose() { for (JRVirtualizationContext virtualizationContext : listenedContexts) { virtualizationContext.removeListener(this); if (log.isDebugEnabled()) { log.debug(id + " unregistered virtualization listener on " + virtualizationContext); } } } public void addDelayedAction(JRFillElement element, JRPrintElement printElement, JREvaluationTime evaluationTime, FillPageKey pageKey) { registerFillElement(element, evaluationTime); ElementEvaluationAction action = new ElementEvaluationAction(element, printElement); addDelayedAction(printElement, action, evaluationTime, pageKey); } protected void registerFillElement(JRFillElement element, JREvaluationTime evaluationTime) { int fillElementId = element.printElementOriginator.getSourceElementId(); if (!fillElements.containsKey(fillElementId)) { fillElements.put(fillElementId, element); if (evaluationTime.getType() == EvaluationTimeEnum.MASTER) { masterFillElementIds.add(fillElementId); } } } protected void registerTransferredId(int sourceId) { if (transferredIds == null) { transferredIds = new HashSet<Integer>(); } // duplicates are handled boolean added = transferredIds.add(sourceId); if (added && log.isDebugEnabled()) { log.debug(id + " transferred id " + sourceId); } } public void addDelayedAction(Object actionKey, EvaluationBoundAction action, JREvaluationTime evaluationTime, FillPageKey pageKey) { if (log.isDebugEnabled()) { log.debug(id + " adding delayed action " + action + " at " + evaluationTime + ", key " + pageKey); } // get the pages map for the evaluation LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pagesMap = actionsMap .get(evaluationTime); fillContext.lockVirtualizationContext(); try { synchronized (pagesMap) { // get the actions map for the current page, creating if it does not yet exist LinkedMap<Object, EvaluationBoundAction> boundElementsMap = pageActionsMap(pagesMap, pageKey); // add the delayed element action to the map boundElementsMap.add(actionKey, action); } } finally { fillContext.unlockVirtualizationContext(); } } protected LinkedMap<Object, EvaluationBoundAction> pageActionsMap( LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map, FillPageKey pageKey) { LinkedMap<Object, EvaluationBoundAction> pageMap = map.get(pageKey); if (pageMap == null) { pageMap = new LinkedMap<Object, EvaluationBoundAction>(); map.put(pageKey, pageMap); registerPage(pageKey.page); } return pageMap; } public void runActions(JREvaluationTime evaluationTime, byte evaluation) throws JRException { if (log.isDebugEnabled()) { log.debug(id + " running delayed actions on " + evaluationTime); } LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pagesMap = actionsMap .get(evaluationTime); boolean hasEntry; do { reportFiller.checkInterrupted(); // locking once per page so that we don't hold the lock for too long // (that would prevent async exporters from getting page data during a long resolve) fillContext.lockVirtualizationContext(); try { synchronized (pagesMap) { // resolve a single page Iterator<Map.Entry<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> pagesIt = pagesMap .entrySet().iterator(); hasEntry = pagesIt.hasNext(); if (hasEntry) { Map.Entry<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pageEntry = pagesIt.next(); int pageIdx = pageEntry.getKey().index; if (log.isDebugEnabled()) { log.debug( id + " running actions for page " + pageEntry.getKey().page + " at " + pageIdx); } StandardBoundActionExecutionContext context = new StandardBoundActionExecutionContext(); context.setCurrentPageIndex(pageIdx); JasperPrint jasperPrint = fillContext.getMasterFiller().getJasperPrint(); context.setTotalPages(jasperPrint.getPages().size()); context.setEvaluationTime(evaluationTime); context.setExpressionEvaluationType(evaluation); LinkedMap<Object, EvaluationBoundAction> boundElementsMap = pageEntry.getValue(); // execute the actions while (!boundElementsMap.isEmpty()) { EvaluationBoundAction action = boundElementsMap.pop(); action.execute(context); } // remove the entry from the pages map pagesIt.remove(); // call the listener to signal that the page has been modified if (reportFiller.fillListener != null) { reportFiller.fillListener.pageUpdated(jasperPrint, pageIdx); } } } } finally { fillContext.unlockVirtualizationContext(); } } while (hasEntry); } public boolean hasDelayedActions(JRPrintPage page) { FillPageKey pageKey = new FillPageKey(page); for (LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map : actionsMap.values()) { fillContext.lockVirtualizationContext(); try { synchronized (map) { LinkedMap<Object, EvaluationBoundAction> boundMap = map.get(pageKey); if (boundMap != null && !boundMap.isEmpty()) { return true; } } } finally { fillContext.unlockVirtualizationContext(); } } return false; } protected boolean hasMasterDelayedActions(JRPrintPage page) { LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> masterActions = actionsMap .get(JREvaluationTime.EVALUATION_TIME_MASTER); FillPageKey pageKey = new FillPageKey(page); fillContext.lockVirtualizationContext(); try { synchronized (masterActions)//FIXME is this necessary? { LinkedMap<Object, EvaluationBoundAction> pageMasterActions = masterActions.get(pageKey); return pageMasterActions != null && !pageMasterActions.isEmpty(); } } finally { fillContext.unlockVirtualizationContext(); } } public void moveActions(FillPageKey fromKey, FillPageKey toKey) { if (log.isDebugEnabled()) { log.debug(id + " moving actions from " + fromKey + " to " + toKey); } for (LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map : actionsMap.values()) { fillContext.lockVirtualizationContext(); try { synchronized (map) { LinkedMap<Object, EvaluationBoundAction> subreportMap = map.remove(fromKey); if (subreportMap != null && !subreportMap.isEmpty()) { LinkedMap<Object, EvaluationBoundAction> masterMap = pageActionsMap(map, toKey); masterMap.addAll(subreportMap); } } } finally { fillContext.unlockVirtualizationContext(); } } } @Override public void beforeExternalization(JRVirtualizable<VirtualElementsData> object) { JRVirtualizationContext virtualizationContext = object.getContext(); virtualizationContext.lock();//already locked in ElementsBlock.beforeExternalization() try { writeElementEvaluations(object); } finally { virtualizationContext.unlock(); } } protected void writeElementEvaluations(final JRVirtualizable<VirtualElementsData> object) { if (log.isDebugEnabled()) { log.debug(id + " setting element evaluation for elements in " + object.getUID()); } JRVirtualPrintPage page = ((VirtualizablePageElements) object).getPage();// ugly but needed for now FillPageKey pageKey = new FillPageKey(page); VirtualElementsData virtualData = object.getVirtualData(); final Map<JREvaluationTime, Map<JRPrintElement, Integer>> evaluations = new LinkedHashMap<>(); ElementEvaluationsCollector collector = new ElementEvaluationsCollector() { @Override public void collect(JRPrintElement printElement, JRFillElement fillElement, JREvaluationTime evaluationTime) { Map<JRPrintElement, Integer> elementEvaluations = evaluations.get(evaluationTime); if (elementEvaluations == null) { // collection delayed evaluations for elements that are about to be externalized. // the evaluations store the ID of the fill elements in order to serialize the data. elementEvaluations = new LinkedHashMap<JRPrintElement, Integer>(); evaluations.put(evaluationTime, elementEvaluations); } elementEvaluations.put(printElement, fillElement.printElementOriginator.getSourceElementId()); } }; doCollectElementEvaluations(page, virtualData.getElements(), collector, false); for (Map.Entry<JREvaluationTime, Map<JRPrintElement, Integer>> evalEntry : evaluations.entrySet()) { JREvaluationTime evaluationTime = evalEntry.getKey(); Map<JRPrintElement, Integer> elementEvaluations = evalEntry.getValue(); // save the evaluations in the virtual data virtualData.setElementEvaluations(id, evaluationTime, elementEvaluations); // add an action for the page so that it gets devirtualized on resolveBoundElements VirtualizedPageEvaluationAction virtualizedAction = new VirtualizedPageEvaluationAction(object, id); LinkedMap<Object, EvaluationBoundAction> pageActions = actionsMap.get(evaluationTime).get(pageKey); pageActions.add(null, virtualizedAction); if (log.isDebugEnabled()) { log.debug(id + " created action " + virtualizedAction); } } } @Override public void afterInternalization(JRVirtualizable<VirtualElementsData> object) { JRVirtualizationContext virtualizationContext = object.getContext(); virtualizationContext.lock(); try { readElementEvaluations(object); } finally { virtualizationContext.unlock(); } } protected void readElementEvaluations(JRVirtualizable<VirtualElementsData> object) { JRVirtualPrintPage page = ((VirtualizablePageElements) object).getPage();// ugly but needed for now FillPageKey pageKey = new FillPageKey(page); for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap .entrySet()) { JREvaluationTime evaluationTime = boundMapEntry.getKey(); LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue(); synchronized (map) { LinkedMap<Object, EvaluationBoundAction> actionsMap = map.get(pageKey); readElementEvaluations(object, id, evaluationTime, actionsMap); if (transferredIds != null) { //FIXMEBOOK does this have any effect on the order of the actions? for (Integer transferredId : transferredIds) { readElementEvaluations(object, transferredId, evaluationTime, actionsMap); } } } } } protected void readElementEvaluations(JRVirtualizable<VirtualElementsData> object, int sourceId, JREvaluationTime evaluationTime, LinkedMap<Object, EvaluationBoundAction> actionsMap) { // get the delayed evaluations from the devirtualized data and add it back // to the filler delayed evaluation maps. VirtualElementsData elementsData = object.getVirtualData(); Map<JRPrintElement, Integer> elementEvaluations = elementsData.getElementEvaluations(sourceId, evaluationTime); if (elementEvaluations != null) { for (Map.Entry<JRPrintElement, Integer> entry : elementEvaluations.entrySet()) { JRPrintElement element = entry.getKey(); int fillElementId = entry.getValue(); JRFillElement fillElement = fillElements.get(fillElementId); if (log.isDebugEnabled()) { log.debug(id + " got evaluation " + evaluationTime + ", source id " + sourceId + ", on " + element + ", from object " + object + ", using " + fillElement); } if (fillElement == null) { throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_ELEMENT_NOT_FOUND, new Object[] { fillElementId }); } // add first so that it will be executed immediately actionsMap.addFirst(element, new ElementEvaluationAction(fillElement, element)); } } } public void moveMasterEvaluations(DelayedFillActions sourceActions, JRPrintPage page, int pageIndex) { FillPageKey sourcePageKey = new FillPageKey(page); FillPageKey destinationPageKey = new FillPageKey(page, pageIndex); moveMasterEvaluations(sourceActions, sourcePageKey, destinationPageKey); } public void moveMasterEvaluations(DelayedFillActions sourceActions, FillPageKey pageKey) { moveMasterEvaluations(sourceActions, pageKey, pageKey); } protected void moveMasterEvaluations(DelayedFillActions sourceActions, FillPageKey sourcePageKey, FillPageKey destinationPageKey) { if (log.isDebugEnabled()) { log.debug(id + " moving master actions from " + sourceActions.id + ", source " + sourcePageKey + ", destination " + destinationPageKey); } fillContext.lockVirtualizationContext(); try { LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> actions = sourceActions.actionsMap .get(JREvaluationTime.EVALUATION_TIME_MASTER); synchronized (actions) { LinkedMap<Object, EvaluationBoundAction> pageActions = actions.remove(sourcePageKey);//FIXMEBOOK deregister virt listener if (pageActions == null || pageActions.isEmpty()) { return; } moveMasterActions(pageActions, destinationPageKey); // copy fill elements Ids for all master actions for (Integer elementId : sourceActions.masterFillElementIds) { if (!fillElements.containsKey(elementId)) { fillElements.put(elementId, sourceActions.fillElements.get(elementId)); masterFillElementIds.add(elementId); } } } } finally { fillContext.unlockVirtualizationContext(); } } protected void moveMasterActions(LinkedMap<Object, EvaluationBoundAction> sourceActions, FillPageKey destinationPageKey) { LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> masterActions = actionsMap .get(JREvaluationTime.EVALUATION_TIME_MASTER); synchronized (masterActions) { LinkedMap<Object, EvaluationBoundAction> masterPageActions = pageActionsMap(masterActions, destinationPageKey); while (!sourceActions.isEmpty()) { Map.Entry<Object, EvaluationBoundAction> entry = sourceActions.popEntry(); Object key = entry.getKey(); EvaluationBoundAction action = entry.getValue(); masterPageActions.add(key, action); actionMoved(action); if (log.isDebugEnabled()) { log.debug(id + " moved action " + action); } } } } protected void actionMoved(EvaluationBoundAction action) { if (action instanceof VirtualizedPageEvaluationAction)//ugly { int sourceId = ((VirtualizedPageEvaluationAction) action).getSourceId(); registerTransferredId(sourceId); } } public void collectElementEvaluations(JRPrintPage page, List<JRPrintElement> elements, final ElementEvaluationsCollector collector) { fillContext.lockVirtualizationContext(); try { doCollectElementEvaluations(page, elements, collector, true); } finally { fillContext.unlockVirtualizationContext(); } } protected void doCollectElementEvaluations(JRPrintPage page, List<JRPrintElement> elements, final ElementEvaluationsCollector collector, boolean clearEmpty) { FillPageKey pageKey = new FillPageKey(page); for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap .entrySet()) { final JREvaluationTime evaluationTime = boundMapEntry.getKey(); LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue(); synchronized (map) { final LinkedMap<Object, EvaluationBoundAction> actionsMap = map.get(pageKey); if (actionsMap != null && !actionsMap.isEmpty()) { // FIXME optimize for pages with a single virtual block // create a deep element visitor PrintElementVisitor<Void> visitor = new UniformPrintElementVisitor<Void>(true) { @Override protected void visitElement(JRPrintElement element, Void arg) { // remove the action from the map because we're saving it as part of the page. // ugly cast but acceptable for now. ElementEvaluationAction action = (ElementEvaluationAction) actionsMap.remove(element); if (action != null) { if (log.isDebugEnabled()) { log.debug(id + " collecting evaluation " + evaluationTime + " of element " + element); } collector.collect(element, action.element, evaluationTime); } } }; for (JRPrintElement element : elements) { element.accept(visitor, null); } if (clearEmpty && actionsMap.isEmpty()) { map.remove(pageKey); } } } } } public void addElementEvaluations(JRPrintPage page, int pageIndex, ElementEvaluationsSource source) { FillPageKey pageKey = new FillPageKey(page, pageIndex); for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap .entrySet()) { JREvaluationTime evaluationTime = boundMapEntry.getKey(); LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue(); synchronized (map) { Map<JRPrintElement, JRFillElement> elementEvaluations = source.getEvaluations(evaluationTime); if (elementEvaluations != null) { LinkedMap<Object, EvaluationBoundAction> actionsMap = pageActionsMap(map, pageKey); for (Map.Entry<JRPrintElement, JRFillElement> entry : elementEvaluations.entrySet()) { JRPrintElement element = entry.getKey(); JRFillElement fillElement = entry.getValue(); if (log.isDebugEnabled()) { log.debug(id + " got evaluation " + evaluationTime + ", source id " + fillElement.printElementOriginator.getSourceElementId() + ", on " + element + ", using " + fillElement); } actionsMap.add(element, new ElementEvaluationAction(fillElement, element)); } } } } } }