/**
* 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.impl;
import org.orbeon.dom.DocumentFactory;
import org.orbeon.dom.Element;
import org.orbeon.oxf.cache.OutputCacheKey;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.OrbeonLocationException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.*;
import org.orbeon.oxf.processor.generator.DOMGenerator;
import org.orbeon.oxf.processor.validation.MSVValidationProcessor;
import org.orbeon.oxf.properties.Properties;
import org.orbeon.oxf.properties.PropertySet;
import org.orbeon.oxf.util.PipelineUtils;
import org.orbeon.oxf.xml.InspectingXMLReceiver;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.xml.XMLReceiverHelper;
import org.orbeon.oxf.xml.dom4j.LocationData;
/**
* Base class for all built-in ProcessorOutput implementations.
*/
public abstract class ProcessorOutputImpl implements ProcessorOutput {
private final Processor processor;
private final Class processorClass;
private final String name;
private ProcessorInput input;
private String id;
private String schema;
private String debugMessage;
private LocationData locationData;
public ProcessorOutputImpl(Class processorClass, String name) {
this.processor = null;
this.processorClass = processorClass;
this.name = name;
}
public ProcessorOutputImpl(Processor processor, String name) {
assert processor != null;
this.processor = processor;
this.processorClass = processor.getClass();
this.name = name;
}
public Processor getProcessor(PipelineContext pipelineContext) {
return processor;
}
public void setInput(ProcessorInput input) {
this.input = input;
}
public ProcessorInput getInput() {
return input;
}
public Class getProcessorClass() {
return processorClass;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getDebugMessage() {
return debugMessage;
}
public LocationData getLocationData() {
return locationData;
}
public void setDebug(String debugMessage) {
this.debugMessage = debugMessage;
}
public void setLocationData(LocationData locationData) {
this.locationData = locationData;
}
/**
* Read method that subclasses must implement.
*
* @param pipelineContext context
* @param xmlReceiver receiver
*/
protected abstract void readImpl(PipelineContext pipelineContext, XMLReceiver xmlReceiver);
/**
* Get the cache key. May be implemented by subclass.
*
* @param pipelineContext context
* @return cache key or null
*/
protected OutputCacheKey getKeyImpl(PipelineContext pipelineContext) {
return null;
}
/**
* Get the cache validity. May be implemented by subclass.
*
* @param pipelineContext context
* @return cache validity or null
*/
protected Object getValidityImpl(PipelineContext pipelineContext) {
return null;
}
private abstract class RuntimeOutputFilter implements ProcessorOutput {
public Processor getProcessor(PipelineContext pipelineContext) {
return ProcessorOutputImpl.this.getProcessor(pipelineContext);
}
// None of the methods implemented here should be called
public void setInput(ProcessorInput processorInput) {
throw new OXFException("This method should never be called!");
}
public ProcessorInput getInput() {
throw new OXFException("This method should never be called!");
}
public void setSchema(String schema) {
throw new OXFException("This method should never be called!");
}
public String getSchema() {
throw new OXFException("This method should never be called!");
}
public Class getProcessorClass() {
throw new OXFException("This method should never be called!");
}
public String getId() {
throw new OXFException("This method should never be called!");
}
public String getName() {
throw new OXFException("This method should never be called!");
}
public void setDebug(String debugMessage) {
throw new OXFException("This method should never be called!");
}
public String getDebugMessage() {
throw new OXFException("This method should never be called!");
}
public void setLocationData(LocationData locationData) {
throw new OXFException("This method should never be called!");
}
public LocationData getLocationData() {
throw new OXFException("This method should never be called!");
}
public void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
throw new OXFException("This method should never be called!");
}
}
/**
* Constructor takes: (1) the input and output of a processor and (2) a
* "previousOutput". It connects the input to the previousOutput and
* when read, reads the output.
*
* Semantic: creates an output that is just like previousOutput on which
* a processor is applies.
*/
private class ConcreteRuntimeOutputFilter extends RuntimeOutputFilter {
private ProcessorOutput processorOutput;
private ProcessorOutput previousProcessorOutput;
private class ForwarderRuntimeOutputOutput extends RuntimeOutputFilter {
@Override
public void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
previousProcessorOutput.read(pipelineContext, xmlReceiver);
}
public OutputCacheKey getKey(PipelineContext pipelineContext) {
return previousProcessorOutput.getKey(pipelineContext);
}
public Object getValidity(PipelineContext pipelineContext) {
return previousProcessorOutput.getValidity(pipelineContext);
}
}
public ConcreteRuntimeOutputFilter(ProcessorInput processorInput,
ProcessorOutput processorOutput,
final ProcessorOutput previousOutput) {
this.processorOutput = processorOutput;
this.previousProcessorOutput = previousOutput;
processorInput.setOutput(new ForwarderRuntimeOutputOutput());
}
@Override
public void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
processorOutput.read(pipelineContext, xmlReceiver);
}
public OutputCacheKey getKey(PipelineContext pipelineContext) {
return processorOutput.getKey(pipelineContext);
}
public Object getValidity(PipelineContext pipelineContext) {
return processorOutput.getValidity(pipelineContext);
}
}
private class TopLevelOutputFilter extends RuntimeOutputFilter {
@Override
public void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
// Read the current processor output
readImpl(pipelineContext, xmlReceiver);
}
public OutputCacheKey getKey(PipelineContext pipelineContext) {
return getKeyImpl(pipelineContext);
}
public Object getValidity(PipelineContext pipelineContext) {
return getValidityImpl(pipelineContext);
}
};
private RuntimeOutputFilter createFilter() {
// Get inspector instance
// Final filter (i.e. at the top, executed last)
RuntimeOutputFilter outputFilter = new TopLevelOutputFilter();
// Handle debug
if (getDebugMessage() != null || (getInput() != null && getInput().getDebugMessage() != null)) {
ProcessorFactory debugProcessorFactory = ProcessorFactoryRegistry.lookup(XMLConstants.DEBUG_PROCESSOR_QNAME);
if (debugProcessorFactory == null)
throw new OXFException("Cannot find debug processor factory for QName: " + XMLConstants.DEBUG_PROCESSOR_QNAME);
for (int i = 0; i < 2; i++) {
String debugMessage = i == 0 ? getDebugMessage() :
getInput() == null ? null : getInput().getDebugMessage();
LocationData debugLocationData = i == 0 ? getLocationData() :
getInput() == null ? null : getInput().getLocationData();
if (debugMessage != null) {
Processor debugProcessor = debugProcessorFactory.createInstance();
debugProcessor.createInput(ProcessorImpl.INPUT_DATA);
debugProcessor.createOutput(ProcessorImpl.OUTPUT_DATA);
// Create config document for Debug processor
final org.orbeon.dom.Document debugConfigDocument;
{
debugConfigDocument = DocumentFactory.createDocument();
Element configElement = debugConfigDocument.addElement("config");
configElement.addElement("message").addText(debugMessage);
if (debugLocationData != null) {
Element systemIdElement = configElement.addElement("system-id");
if (debugLocationData.file() != null)
systemIdElement.addText(debugLocationData.file());
configElement.addElement("line").addText(Integer.toString(debugLocationData.line()));
configElement.addElement("column").addText(Integer.toString(debugLocationData.col()));
}
}
final DOMGenerator dg = new DOMGenerator
( debugConfigDocument, "debug filter"
, DOMGenerator.ZeroValidity, DOMGenerator.DefaultContext );
PipelineUtils.connect( dg, "data", debugProcessor, "config");
final ProcessorOutput dbgOut
= debugProcessor.getOutputByName( ProcessorImpl.OUTPUT_DATA );
final ProcessorInput dbgIn = debugProcessor.getInputByName( ProcessorImpl.INPUT_DATA );
outputFilter = new ConcreteRuntimeOutputFilter( dbgIn, dbgOut, outputFilter);
}
}
}
// The PropertySet can be null during properties initialization. This should be one of the
// rare places where this should be tested on.
final PropertySet propertySet = Properties.instance().getPropertySet();
// Create and hook-up output validation processor if needed
final Boolean isUserValidation = (propertySet == null) ? null : propertySet.getBoolean(ProcessorImpl.USER_VALIDATION_FLAG, true);
if (isUserValidation != null && isUserValidation.booleanValue() && getSchema() != null) {
final Processor outputValidator = new MSVValidationProcessor(getSchema());
// Create data input and output
final ProcessorInput input = outputValidator.createInput(ProcessorImpl.INPUT_DATA);
final ProcessorOutput output = outputValidator.createOutput(ProcessorImpl.OUTPUT_DATA);
// Create and connect config input
final Processor resourceGenerator = PipelineUtils.createURLGenerator(getSchema());
PipelineUtils.connect(resourceGenerator, ProcessorImpl.OUTPUT_DATA, outputValidator, MSVValidationProcessor.INPUT_SCHEMA);
PipelineUtils.connect(MSVValidationProcessor.NO_DECORATION_CONFIG, ProcessorImpl.OUTPUT_DATA,
outputValidator, ProcessorImpl.INPUT_CONFIG);
outputFilter = new ConcreteRuntimeOutputFilter(input, output, outputFilter);
}
// Hook-up input validation processor if needed
if (isUserValidation != null && isUserValidation.booleanValue() &&
getInput() != null && getInput().getSchema() != null) {
final Processor inputValidator = new MSVValidationProcessor(getInput().getSchema());
// Create data input and output
final ProcessorInput input = inputValidator.createInput(ProcessorImpl.INPUT_DATA);
final ProcessorOutput output = inputValidator.createOutput(ProcessorImpl.OUTPUT_DATA);
// Create and connect config input
final Processor resourceGenerator = PipelineUtils.createURLGenerator(getInput().getSchema());
PipelineUtils.connect(resourceGenerator, ProcessorImpl.OUTPUT_DATA, inputValidator, MSVValidationProcessor.INPUT_SCHEMA);
PipelineUtils.connect(MSVValidationProcessor.NO_DECORATION_CONFIG, ProcessorImpl.OUTPUT_DATA,
inputValidator, ProcessorImpl.INPUT_CONFIG);
outputFilter = new ConcreteRuntimeOutputFilter(input, output, outputFilter);
}
// Perform basic inspection of SAX events
Boolean isSAXInspection = (propertySet == null) ? null : propertySet.getBoolean(ProcessorImpl.SAX_INSPECTION_FLAG, false);
if (isSAXInspection != null && isSAXInspection.booleanValue()) {
final RuntimeOutputFilter previousOutputFilter = outputFilter;
outputFilter = new RuntimeOutputFilter() {
@Override
public void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
InspectingXMLReceiver inspectingXMLReceiver = new InspectingXMLReceiver(xmlReceiver);
previousOutputFilter.read(pipelineContext, inspectingXMLReceiver);
}
public OutputCacheKey getKey(PipelineContext pipelineContext) {
return previousOutputFilter.getKey(pipelineContext);
}
public Object getValidity(PipelineContext pipelineContext) {
return previousOutputFilter.getValidity(pipelineContext);
}
};
}
return outputFilter;
}
/**
* NOTE: We should never use processor instance variables. Here, the creation may not be thread safe in that
* the filter may be initialized several times. This should not be a real problem, and the execution should not
* be problematic either. It may be safer to synchronize getRuntimeFilter().
*/
private RuntimeOutputFilter outputFilter = null;
private RuntimeOutputFilter getRuntimeFilter() {
if (outputFilter == null)
outputFilter = createFilter();
return outputFilter;
}
public final void read(PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
try {
// Delegate
getRuntimeFilter().read(pipelineContext, xmlReceiver);
// NOTE: Not sure why we used to catch and log AbstractMethodError here, but we should not!
} catch (Exception e) {
throw OrbeonLocationException.wrapException(e, getLocationData());
}
}
public final OutputCacheKey getKey(PipelineContext pipelineContext) {
return getRuntimeFilter().getKey(pipelineContext);
// // In debug mode, force read of input if not read yet
// if (pipelineContext.isKeyDebugging() && pipelineContext.getTraceForUpdate() != null) {
// final TraceEntry traceEntry = pipelineContext.getTraceForUpdate().getTraceEntry(this);
// if (!traceEntry.outputReadCalled)
// read(pipelineContext, new XMLReceiverAdapter());
// }
//
// // Delegate
// final OutputCacheKey outputCacheKey = getRuntimeFilter().getKey(pipelineContext);
//
// // Update trace if needed
// if (pipelineContext.isKeyDebugging() && pipelineContext.getTraceForUpdate() != null) {
// final TraceEntry traceEntry = pipelineContext.getTraceForUpdate().getTraceEntry(this);
// traceEntry.outputGetKeyCalled(outputCacheKey == null);
// }
//
// return outputCacheKey;
}
public final Object getValidity(PipelineContext pipelineContext) {
return getRuntimeFilter().getValidity(pipelineContext);
}
public void toXML(PipelineContext pipelineContext, XMLReceiverHelper helper) {
final ProcessorInput input = getInput();
// Input connected to the output
if (input != null) {
helper.startElement("input", new String[] {
"name", input.getName(),
"processor-class", input.getProcessorClass() != null ? input.getProcessorClass().getName() : null,
"processor-name", input.getProcessor(pipelineContext) != null && input.getProcessor(pipelineContext).getName() != null ? input.getProcessor(pipelineContext).getName().getQualifiedName() : null,
"processor-object", input.getProcessor(pipelineContext) != null ? Integer.toString(input.getProcessor(pipelineContext).getSequenceNumber()) : null,
"system-id", (input.getLocationData() != null) ? input.getLocationData().file() : null,
"line", (input.getLocationData() != null) ? Integer.toString(input.getLocationData().line()) : null,
"column", (input.getLocationData() != null) ? Integer.toString(input.getLocationData().col()) : null
});
}
// Output connected to the input
helper.element("output", new String[] {
"id", getId(),
"name", getName(),
"processor-class", getProcessorClass() != null ? getProcessorClass().getName() : null,
"processor-name", getProcessor(pipelineContext) != null && getProcessor(pipelineContext).getName() != null ? getProcessor(pipelineContext).getName().getQualifiedName() : null,
"processor-object", getProcessor(pipelineContext) != null ? Integer.toString(getProcessor(pipelineContext).getSequenceNumber()) : null,
"system-id", (getLocationData() != null) ? getLocationData().file() : null,
"line", (getLocationData() != null) ? Integer.toString(getLocationData().line()) : null,
"column", (getLocationData() != null) ? Integer.toString(getLocationData().col()) : null,
"schema", getSchema(),
"debug", getDebugMessage()
});
if (input != null) {
helper.endElement();
}
}
}