/** * Copyright (C) 2010 Orbeon, Inc. * * This program 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 * 2.1 of the License, or (at your option) any later version. * * This program 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. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor.pipeline; import org.apache.log4j.Logger; import org.orbeon.oxf.cache.OutputCacheKey; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.xml.XMLReceiver; import org.orbeon.oxf.processor.*; import org.orbeon.oxf.util.LoggerFactory; import org.orbeon.oxf.xml.SAXStore; import org.orbeon.oxf.xml.dom4j.LocationData; import org.xml.sax.SAXException; /** * This internal processor handles the tee-ing functionality of XPL, i.e. sending an XML infoset to multiple readers. */ public class TeeProcessor extends ProcessorImpl { private static final Logger logger = LoggerFactory.createLogger(TeeProcessor.class); private Exception creationException; private Exception resetException; private ProcessorKey resetProcessorKey; public TeeProcessor(LocationData locationData) { if (logger.isDebugEnabled()) { creationException = new ValidationException("", locationData); } addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA)); addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA)); } /** * Standard createOutput(). */ @Override public ProcessorOutput createOutput(String name) { return createOutput(name, false); } /** * Tee-specific createOutput(). */ public ProcessorOutput createOutput(String name, boolean isMultipleReads) { final ProcessorOutput output = new TeeProcessorOutputImpl(name, isMultipleReads); addOutput(name, output); return output; } public class TeeProcessorOutputImpl extends ProcessorOutputImpl { private boolean isMultipleReads; private TeeProcessorOutputImpl(String name, boolean isMultipleReads) { super(TeeProcessor.this, name); this.isMultipleReads = isMultipleReads; } @Override public void readImpl(PipelineContext context, XMLReceiver xmlReceiver) { try { final State state = (State) getState(context); if (state.store == null) { // Tee hasn't been read yet if (state.stateWasCleared) { final ProcessorOutput output = getInputByName(INPUT_DATA).getOutput(); logger.error("Tee state was cleared and re-read for output: " + output.getName()); } // Create SAXStore and read input through it final ProcessorInput input = getInputByName(INPUT_DATA); state.store = new SAXStore(xmlReceiver); readInputAsSAX(context, input, state.store); } else { state.store.replay(xmlReceiver); } // If this output can be read only once, increase read count if (!isMultipleReads) { state.readCount++; } // If possible, free the SAXStore after the last read freeSAXStoreIfNeeded(state); } catch (SAXException e) { throw new OXFException(e); } } /** * This is called specifically by p:for-each once an input has been entirely read. */ public void doneReading(PipelineContext context) { // This output can be read more than once, so increase read count only when needed final State state = (State) getState(context); state.readCount++; // If possible, free the SAXStore after the last read freeSAXStoreIfNeeded(state); } private void freeSAXStoreIfNeeded(State state) { if (state.readCount == getOutputCount()) { final SAXStore freedStore = state.store; state.store = null; state.stateWasCleared = true; final ProcessorOutput output = getInputByName(INPUT_DATA).getOutput(); if (logger.isDebugEnabled()) { final long saxStoreSize = freedStore.getApproximateSize(); logger.debug("Freed SAXStore for output id: " + output.getName() + "; approximate size: " + saxStoreSize + " bytes"); } } } @Override public OutputCacheKey getKeyImpl(PipelineContext pipelineContext) { final State state; try { state = (State) getState(pipelineContext); } catch (OXFException e) { if (logger.isDebugEnabled()) { logger.error("creation", creationException); logger.error("reset", resetException); logger.error("current processor key: " + getProcessorKey(pipelineContext)); logger.error("reset processor key: " + resetProcessorKey); } throw e; } if (state.outputCacheKey == null) { final ProcessorOutput output = getInputByName(INPUT_DATA).getOutput(); state.outputCacheKey = output.getKey(pipelineContext); } return state.outputCacheKey; } @Override public Object getValidityImpl(PipelineContext pipelineContext) { final State state = (State) getState(pipelineContext); if (state.validity == null) { final ProcessorOutput output = getInputByName(INPUT_DATA).getOutput(); state.validity = output.getValidity(pipelineContext); } return state.validity; } } @Override public void reset(PipelineContext context) { if (logger.isDebugEnabled()) { resetException = new Exception(Integer.toString(hashCode())); resetProcessorKey = getProcessorKey(context); } setState(context, new State()); } private static class State { public SAXStore store; public int readCount; public OutputCacheKey outputCacheKey; public Object validity; public boolean stateWasCleared; } }