/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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 library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.processor.relational;
import java.lang.reflect.Array;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import net.sf.saxon.Configuration;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.sxpath.XPathDynamicContext;
import net.sf.saxon.sxpath.XPathExpression;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.ConversionResult;
import net.sf.saxon.type.Converter;
import net.sf.saxon.type.ValidationException;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.CalendarValue;
import net.sf.saxon.value.StringValue;
import org.teiid.api.exception.query.ExpressionEvaluationException;
import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager.TupleSourceType;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.TupleBuffer;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.ArrayImpl;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.TransformationException;
import org.teiid.core.types.XMLType;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.function.FunctionDescriptor;
import org.teiid.query.function.source.XMLSystemFunctions;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.XMLTable;
import org.teiid.query.sql.lang.XMLTable.XMLColumn;
import org.teiid.query.util.CommandContext;
import org.teiid.query.xquery.saxon.SaxonXQueryExpression.Result;
import org.teiid.query.xquery.saxon.SaxonXQueryExpression.RowProcessor;
import org.teiid.query.xquery.saxon.XQueryEvaluator;
/**
* Handles xml table processing.
*
* When streaming the results will be fully built and stored in a buffer
* before being returned
*/
public class XMLTableNode extends SubqueryAwareRelationalNode implements RowProcessor {
private static Map<Class<?>, BuiltInAtomicType> typeMapping = new HashMap<Class<?>, BuiltInAtomicType>();
static {
typeMapping.put(DataTypeManager.DefaultDataClasses.TIMESTAMP, BuiltInAtomicType.DATE_TIME);
typeMapping.put(DataTypeManager.DefaultDataClasses.TIME, BuiltInAtomicType.TIME);
typeMapping.put(DataTypeManager.DefaultDataClasses.DATE, BuiltInAtomicType.DATE);
typeMapping.put(DataTypeManager.DefaultDataClasses.FLOAT, BuiltInAtomicType.FLOAT);
typeMapping.put(DataTypeManager.DefaultDataClasses.DOUBLE, BuiltInAtomicType.DOUBLE);
typeMapping.put(DataTypeManager.DefaultDataClasses.BLOB, BuiltInAtomicType.HEX_BINARY);
typeMapping.put(DataTypeManager.DefaultDataClasses.VARBINARY, BuiltInAtomicType.HEX_BINARY);
}
private static TeiidRuntimeException EARLY_TERMINATION = new TeiidRuntimeException();
private XMLTable table;
private List<XMLColumn> projectedColumns;
private Result result;
private long rowCount = 0;
private Item item;
private TupleBuffer buffer;
private enum State {
BUILDING,
AVAILABLE,
DONE
};
private State state = State.BUILDING;
private volatile TeiidRuntimeException asynchException;
private long outputRow = 1;
private boolean usingOutput;
private int rowLimit = -1;
private boolean streaming;
public XMLTableNode(int nodeID) {
super(nodeID);
}
@Override
public synchronized void closeDirect() {
super.closeDirect();
if(this.buffer != null) {
if (!usingOutput) {
this.buffer.remove();
}
this.buffer = null;
}
reset();
}
@Override
public synchronized void reset() {
super.reset();
if (this.result != null) {
result.close();
result = null;
}
item = null;
rowCount = 0;
outputRow = 1;
usingOutput = false;
this.buffer = null;
this.state = State.BUILDING;
this.asynchException = null;
this.rowLimit = -1;
}
public void setTable(XMLTable table) {
this.table = table;
}
public void setProjectedColumns(List<XMLColumn> projectedColumns) {
this.projectedColumns = projectedColumns;
}
@Override
public XMLTableNode clone() {
XMLTableNode clone = new XMLTableNode(getID());
this.copyTo(clone);
clone.setTable(table);
clone.setProjectedColumns(projectedColumns);
return clone;
}
@Override
public void open() throws TeiidComponentException, TeiidProcessingException {
super.open();
if (getParent() instanceof LimitNode) {
LimitNode parent = (LimitNode)getParent();
if (parent.getLimit() > 0) {
rowLimit = parent.getLimit() + parent.getOffset();
}
}
streaming = this.table.getXQueryExpression().isStreaming();
}
@Override
protected synchronized TupleBatch nextBatchDirect() throws BlockedException,
TeiidComponentException, TeiidProcessingException {
evaluate(false);
if (streaming) {
while (state == State.BUILDING) {
try {
this.wait();
} catch (InterruptedException e) {
throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30169, e);
}
}
unwrapException(asynchException);
TupleBatch batch = this.buffer.getBatch(outputRow);
outputRow = batch.getEndRow() + 1;
if (state != State.DONE && !batch.getTerminationFlag()) {
state = hasNextBatch()?State.AVAILABLE:State.BUILDING;
}
return batch;
}
while (!isBatchFull() && !isLastBatch()) {
if (item == null) {
try {
item = result.iter.next();
} catch (XPathException e) {
throw new TeiidProcessingException(QueryPlugin.Event.TEIID30170, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30170, e.getMessage()));
}
rowCount++;
if (item == null) {
terminateBatches();
break;
}
}
addBatchRow(processRow());
if (rowCount == rowLimit) {
terminateBatches();
break;
}
}
return pullBatch();
}
private void evaluate(final boolean useFinalBuffer) throws TeiidComponentException,
ExpressionEvaluationException, BlockedException,
TeiidProcessingException {
if (result != null || this.buffer != null) {
return;
}
setReferenceValues(this.table);
final HashMap<String, Object> parameters = new HashMap<String, Object>();
Evaluator eval = getEvaluator(Collections.emptyMap());
eval.evaluateParameters(this.table.getPassing(), null, parameters);
final Object contextItem;
if (parameters.containsKey(null)) {
contextItem = parameters.remove(null);
//null context item mean no rows
if (contextItem == null) {
result = new Result();
result.iter = EmptyIterator.emptyIterator();
streaming = false;
return;
}
} else {
contextItem = null;
}
if (this.table.getXQueryExpression().isStreaming()) {
if (this.buffer == null) {
this.buffer = this.getBufferManager().createTupleBuffer(getOutputElements(), getConnectionID(), TupleSourceType.PROCESSOR);
if (!useFinalBuffer) {
this.buffer.setForwardOnly(true);
}
}
Runnable r = new Runnable() {
@Override
public void run() {
try {
XQueryEvaluator.evaluateXQuery(table.getXQueryExpression(), contextItem, parameters, XMLTableNode.this, getContext());
} catch (TeiidRuntimeException e) {
if (e != EARLY_TERMINATION) {
asynchException = e;
}
} catch (Throwable e) {
asynchException = new TeiidRuntimeException(e);
} finally {
synchronized (XMLTableNode.this) {
if (buffer != null && asynchException == null) {
try {
buffer.close();
} catch (TeiidComponentException e) {
asynchException = new TeiidRuntimeException(e);
}
}
state = State.DONE;
XMLTableNode.this.notifyAll();
}
}
}
};
this.getContext().getExecutor().execute(r);
return;
}
try {
result = XQueryEvaluator.evaluateXQuery(this.table.getXQueryExpression(), contextItem, parameters, null, this.getContext());
} catch (TeiidRuntimeException e) {
unwrapException(e);
}
}
private List<?> processRow() throws ExpressionEvaluationException, BlockedException,
TeiidComponentException, TeiidProcessingException {
List<Object> tuple = new ArrayList<Object>(projectedColumns.size());
for (XMLColumn proColumn : projectedColumns) {
if (proColumn.isOrdinal()) {
if (rowCount > Integer.MAX_VALUE) {
throw new TeiidRuntimeException(new TeiidProcessingException(QueryPlugin.Event.TEIID31174, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31174)));
}
tuple.add((int)rowCount);
} else {
try {
XPathExpression path = proColumn.getPathExpression();
XPathDynamicContext dynamicContext = path.createDynamicContext(item);
SequenceIterator pathIter = path.iterate(dynamicContext);
Item colItem = pathIter.next();
if (colItem == null) {
if (proColumn.getDefaultExpression() != null) {
tuple.add(getEvaluator(Collections.emptyMap()).evaluate(proColumn.getDefaultExpression(), null));
} else {
tuple.add(null);
}
continue;
}
if (proColumn.getSymbol().getType() == DataTypeManager.DefaultDataClasses.XML) {
XMLType value = table.getXQueryExpression().createXMLType(pathIter.getAnother(), this.getBufferManager(), false, getContext());
tuple.add(value);
continue;
}
if (proColumn.getSymbol().getType().isArray()) {
ArrayList<Object> vals = new ArrayList<Object>();
vals.add(getValue(proColumn.getSymbol().getType().getComponentType(), colItem, this.table.getXQueryExpression().getConfig(), getContext()));
Item next = null;
while ((next = pathIter.next()) != null) {
vals.add(getValue(proColumn.getSymbol().getType().getComponentType(), next, this.table.getXQueryExpression().getConfig(), getContext()));
}
Object value = new ArrayImpl(vals.toArray((Object[]) Array.newInstance(proColumn.getSymbol().getType().getComponentType(), vals.size())));
tuple.add(value);
continue;
} else if (pathIter.next() != null) {
throw new TeiidProcessingException(QueryPlugin.Event.TEIID30171, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30171, proColumn.getName()));
}
Object value = getValue(proColumn.getSymbol().getType(), colItem, this.table.getXQueryExpression().getConfig(), getContext());
tuple.add(value);
} catch (XPathException e) {
throw new TeiidProcessingException(QueryPlugin.Event.TEIID30172, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30172, proColumn.getName()));
}
}
}
item = null;
return tuple;
}
public static Object getValue(Class<?> type,
Item colItem, Configuration config, CommandContext context) throws XPathException,
ValidationException, TransformationException {
Object value = colItem;
if (value instanceof AtomicValue) {
value = getValue((AtomicValue)colItem, context);
} else if (value instanceof Item) {
Item i = (Item)value;
if (XMLSystemFunctions.isNull(i)) {
return null;
}
BuiltInAtomicType bat = typeMapping.get(type);
if (bat != null) {
AtomicValue av = new StringValue(i.getStringValueCS());
ConversionResult cr = Converter.convert(av, bat, config.getConversionRules());
value = cr.asAtomic();
value = getValue((AtomicValue)value, context);
if (value instanceof Item) {
value = ((Item)value).getStringValue();
}
} else {
value = i.getStringValue();
}
}
return FunctionDescriptor.importValue(value, type);
}
static private Object getValue(AtomicValue value, CommandContext context) throws XPathException {
if (value instanceof CalendarValue) {
CalendarValue cv = (CalendarValue)value;
if (!cv.hasTimezone()) {
TimeZone tz = context.getServerTimeZone();
int tzMin = tz.getRawOffset()/60000;
if (tz.getDSTSavings() > 0) {
tzMin = tz.getOffset(cv.getCalendar().getTimeInMillis())/60000;
}
cv.setTimezoneInMinutes(tzMin);
Calendar cal = cv.getCalendar();
return new Timestamp(cal.getTime().getTime());
}
}
return SequenceTool.convertToJava(value);
}
@Override
public synchronized void processRow(NodeInfo row) {
if (isClosed()) {
throw EARLY_TERMINATION;
}
assert this.state != State.DONE;
this.item = row;
rowCount++;
try {
this.buffer.addTuple(processRow());
if (this.buffer.getRowCount() == rowLimit) {
throw EARLY_TERMINATION;
}
if (state == State.BUILDING && hasNextBatch()) {
this.state = State.AVAILABLE;
this.notifyAll();
}
} catch (TeiidException e) {
throw new TeiidRuntimeException(e);
}
}
private boolean hasNextBatch() {
return this.outputRow + this.buffer.getBatchSize() <= rowCount + 1;
}
@Override
public Collection<? extends LanguageObject> getObjects() {
return this.table.getPassing();
}
@Override
public PlanNode getDescriptionProperties() {
PlanNode props = super.getDescriptionProperties();
AnalysisRecord.addLanaguageObjects(props, AnalysisRecord.PROP_TABLE_FUNCTION, Arrays.asList(this.table));
props.addProperty(AnalysisRecord.PROP_STREAMING, String.valueOf(this.table.getXQueryExpression().isStreaming()));
return props;
}
}