/**
* 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.sql;
import org.orbeon.dom.Node;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.DatabaseContext;
import org.orbeon.oxf.processor.Datasource;
import org.orbeon.oxf.processor.sql.delegates.SQLProcessorOracleJDBC4Delegate;
import org.orbeon.oxf.processor.sql.delegates.SQLProcessorStandardDelegate;
import org.orbeon.oxf.properties.PropertySet;
import org.orbeon.oxf.util.SecureUtils;
import org.orbeon.oxf.xml.DeferredXMLReceiver;
import org.orbeon.oxf.xml.XPathXMLReceiver;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.xml.sax.Locator;
import org.xml.sax.helpers.NamespaceSupport;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
/**
* Interpreter context for the SQL processor.
*/
public class SQLProcessorInterpreterContext extends DatabaseContext {
private PropertySet propertySet;
// Locator for datasource declaration, if any
private Locator documentLocator;
// Either one of those two must be set
private String jndiName;
private Datasource datasource;
private PipelineContext pipelineContext;
private Node input;
private XPathXMLReceiver xpathReceiver;
private DeferredXMLReceiver output;
private NamespaceSupport namespaceSupport;
private List executionContextStack;
private List currentNodes;
private List<SQLFunctionLibrary.SQLFunctionContext> functionContextStack = new ArrayList<SQLFunctionLibrary.SQLFunctionContext>();
public static final String SQL_PROCESSOR_CONTEXT = "sql-processor-context"; // used by SQLProcessor and related
public SQLProcessorInterpreterContext(PropertySet propertySet) {
this.propertySet = propertySet;
}
public PropertySet getPropertySet() {
return propertySet;
}
private static class ExecutionContext implements Cloneable {
public ResultSet resultSet;
public PreparedStatement preparedStatement;
public String statementString;
public String statementSHA;
public boolean emptyResultSet;
public boolean gotResults;
public int updateCount;
public int columnIndex;
public String columnName;
public String columnType;
public String columnValue;
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public void pushContext() {
if (executionContextStack == null)
executionContextStack = new ArrayList();
try {
if (executionContextStack.size() == 0) {
// Start with empty context
executionContextStack.add(new ExecutionContext());
} else {
// Clone context data
executionContextStack.add(getExecutionContext(0).clone());
}
} catch (CloneNotSupportedException e) {
// Should not happen
throw new OXFException(e);
}
}
private ExecutionContext getExecutionContext(int level) {
return (ExecutionContext) executionContextStack.get(executionContextStack.size() - 1 - level);
}
public void popContext() {
executionContextStack.remove(executionContextStack.size() - 1);
}
public PipelineContext getPipelineContext() {
return pipelineContext;
}
public void setPipelineContext(PipelineContext pipelineContext) {
this.pipelineContext = pipelineContext;
}
public void setConnection(Locator documentLocator, String datasourceName) throws Exception {
this.documentLocator = documentLocator;
jndiName = "jdbc/" + datasourceName;
}
public DatabaseDelegate getDelegate() {
// Try to obtain delegate from context
Context context = getContext(pipelineContext);
String delegateKey = (jndiName != null) ? jndiName : datasource.toString();
DatabaseDelegate databaseDelegate = (DatabaseDelegate) context.delegates.get(delegateKey);
if (databaseDelegate == null) {
// Delegate needs to be created
try {
DatabaseMetaData databaseMetaData = getConnection().getMetaData();
String productName = databaseMetaData.getDatabaseProductName();
Class clazz = null;
if ("oracle".equalsIgnoreCase(productName)) {
// Try Tomcat delegate
try {
getClass().getClassLoader().loadClass("org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement");
clazz = getClass().getClassLoader().loadClass("org.orbeon.oxf.processor.sql.delegates.SQLProcessorOracleTomcatDelegate");
SQLProcessor.logger.info("Using Oracle Tomcat delegate.");
} catch (Throwable t) {
// Ignore
}
// Try JBoss 7 delegate
// - since JBoss 7 supports JDBC 4, we shouldn't need this, and we should just be able to use SQLProcessorOracleJDBC4Delegate
if (clazz == null) {
try {
getClass().getClassLoader().loadClass("org.jboss.jca.adapters.jdbc.WrappedPreparedStatement");
clazz = getClass().getClassLoader().loadClass("org.orbeon.oxf.processor.sql.delegates.SQLProcessorOracleJBoss7Delegate");
SQLProcessor.logger.info("Using Oracle JBoss 7 delegate.");
} catch (Throwable t) {
// Ignore
}
}
// Try JBoss 6 delegate
if (clazz == null) {
try {
getClass().getClassLoader().loadClass("org.jboss.resource.adapter.jdbc.WrappedPreparedStatement");
clazz = getClass().getClassLoader().loadClass("org.orbeon.oxf.processor.sql.delegates.SQLProcessorOracleJBoss6Delegate");
SQLProcessor.logger.info("Using Oracle JBoss 6 delegate.");
} catch (Throwable t) {
// Ignore
}
}
// Then use the JDBC4 delegate
if (clazz == null) {
clazz = SQLProcessorOracleJDBC4Delegate.class;
SQLProcessor.logger.info("Using Oracle JDBC4 delegate.");
}
} else {
// For JDBC drivers that properly implement the API
clazz = SQLProcessorStandardDelegate.class;
}
databaseDelegate = (DatabaseDelegate) clazz.newInstance();
getContext(pipelineContext).delegates.put(delegateKey, databaseDelegate);
} catch (Exception e) {
throw new OXFException(e);
}
}
return databaseDelegate;
}
public void pushCurrentNode(Node node) {
currentNodes.add(node);
}
public Node popCurrentNode() {
return (Node) currentNodes.remove(currentNodes.size() - 1);
}
public Node getCurrentNode() {
return (Node) currentNodes.get(currentNodes.size() - 1);
}
public void pushFunctionContext(SQLFunctionLibrary.SQLFunctionContext ctx) {
functionContextStack.add(ctx);
}
public SQLFunctionLibrary.SQLFunctionContext popFunctionContext() {
return functionContextStack.remove(functionContextStack.size() - 1);
}
public SQLFunctionLibrary.SQLFunctionContext getFunctionContextOrNull() {
return functionContextStack.isEmpty() ? null : functionContextStack.get(functionContextStack.size() - 1);
}
public void setResultSet(ResultSet resultSet) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.resultSet = resultSet;
}
public void setStatement(PreparedStatement stmt) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.preparedStatement = stmt;
}
public void setStatementString(String statementString) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.statementString = statementString;
}
public void setColumnContext(int index, String name, String type, String value) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.columnIndex = index;
executionContext.columnName = name;
executionContext.columnType = type;
executionContext.columnValue = value;
}
public int getColumnIndex() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.columnIndex;
}
public String getColumnName() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.columnName;
}
public String getColumnType() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.columnType;
}
public String getColumnValue() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.columnValue;
}
public ResultSet getResultSet() {
return getResultSet(0);
}
public ResultSet getResultSet(int level) {
final ExecutionContext executionContext = getExecutionContext(level);
return executionContext.resultSet;
}
public PreparedStatement getStatement(int level) {
final ExecutionContext executionContext = getExecutionContext(level);
return executionContext.preparedStatement;
}
public String getStatementString(int level) {
final ExecutionContext executionContext = getExecutionContext(level);
return executionContext.statementString;
}
public String getStatementString() {
return getStatementString(0);
}
public String getStatementSHA(int level) {
final ExecutionContext executionContext = getExecutionContext(level);
if (executionContext.statementSHA == null) {
final String fullSHA = SecureUtils.digestString(executionContext.statementString, "SHA1", "hex");
executionContext.statementSHA = fullSHA.substring(0, 7);
}
return executionContext.statementSHA;
}
public String getStatementSHA() {
return getStatementSHA(0);
}
public Connection getConnection() {
if (jndiName != null) {
// Connection was configured with as a JDBC datasource
try {
return getConnection(pipelineContext, jndiName);
} catch (RuntimeException e) {
if (documentLocator != null)
throw new ValidationException(e, new LocationData(documentLocator));
else
throw e;
}
} else if (datasource != null) {
// Connection was configured with an internal datasource
return getConnection(pipelineContext, datasource);
} else {
throw new OXFException("No datasource configured, cannot get connection to database.");
}
}
public Node getInput() {
return input;
}
/**
* Set the input document. This also determines the default current node
* for all XPath expressions.
*/
public void setInput(Node input) {
this.input = input;
currentNodes = new ArrayList();
currentNodes.add(input);
}
/**
* Set optional Datasource object. If null, the configuration has to contain a reference to a
* datasource.
*
* @param datasource Datasource object or null
*/
public void setDatasource(Datasource datasource) {
this.datasource = datasource;
}
public XPathXMLReceiver getXPathContentHandler() {
return xpathReceiver;
}
public void setXPathContentHandler(XPathXMLReceiver xpathReceiver) {
this.xpathReceiver = xpathReceiver;
}
public DeferredXMLReceiver getOutput() {
return output;
}
public void setOutput(DeferredXMLReceiver output) {
this.output = output;
}
public boolean isEmptyResultSet() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.emptyResultSet;
}
public void setEmptyResultSet(boolean emptyResultSet) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.emptyResultSet = emptyResultSet;
}
public boolean isGotResults() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.gotResults;
}
public void setGotResults(boolean gotResults) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.gotResults = gotResults;
}
public int getUpdateCount() {
final ExecutionContext executionContext = getExecutionContext(0);
return executionContext.updateCount;
}
public void setUpdateCount(int updateCount) {
final ExecutionContext executionContext = getExecutionContext(0);
executionContext.updateCount = updateCount;
}
public NamespaceSupport getNamespaceSupport() {
return namespaceSupport;
}
public Map getPrefixesMap() {
Map prefixesMap = new HashMap();
for (Enumeration e = namespaceSupport.getPrefixes(); e.hasMoreElements();) {
String prefix = (String) e.nextElement();
prefixesMap.put(prefix, namespaceSupport.getURI(prefix));
}
return prefixesMap;
}
public void setNamespaceSupport(NamespaceSupport namespaceSupport) {
this.namespaceSupport = namespaceSupport;
}
// public void processNamespaces(String uri, String localname, String qName, Attributes attributes) {
// for (int i = 0; i < attributes.getLength(); i++) {
// String name = attributes.getQName(i);
// if (name.startsWith("xmlns:")) {
// maybeDeclarePrefix(name.substring(6), attributes.getValue(i));
// }
// }
// int index = qName.indexOf(':');
// if (index != -1) {
// maybeDeclarePrefix(qName.substring(0, index), uri);
// }
// }
// private void maybeDeclarePrefix(String prefix, String uri) {
// String supportURI = namespaceSupport.getURI(prefix);
// if (!uri.equals(supportURI)) {
// namespaceSupport.declarePrefix(prefix, uri);
// }
// }
public void declarePrefix(String prefix, String uri) {
namespaceSupport.declarePrefix(prefix, uri);
}
private Context getContext(PipelineContext pipelineContext) {
Context context = (Context) pipelineContext.getAttribute(SQL_PROCESSOR_CONTEXT);
if (context == null) {
context = new Context();
pipelineContext.setAttribute(SQL_PROCESSOR_CONTEXT, context);
}
return context;
}
private static class Context {
// Map datasource names to delegates
public Map delegates = new HashMap();
}
}