/**
* Copyright (C) 2012 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.XQuery;
import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.xml.XMLParsing;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.processor.ProcessorImpl;
import org.orbeon.oxf.processor.ProcessorInputOutputInfo;
import org.orbeon.oxf.processor.ProcessorOutput;
import org.orbeon.oxf.processor.transformer.TransformerURIResolver;
import org.orbeon.oxf.xml.XMLReceiverHelper;
import org.orbeon.oxf.xml.SimpleForwardingXMLReceiver;
import org.orbeon.saxon.xqj.SaxonXQDataSource;
import org.xml.sax.SAXException;
import javax.xml.namespace.QName;
import javax.xml.xquery.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
/**
* XQuery client/server processor, typically based on XQJ.
*/
public class XQueryProcessor extends ProcessorImpl {
public static final String XQUERY_NAMESPACE_URI = "http://www.orbeon.org/oxf/xml/xquery";
private static Logger logger = Logger.getLogger(XQueryProcessor.class);
private static HashMap<String, String> knownImplementations = initKnownImplementations();
private static HashMap<String, String> initKnownImplementations() {
HashMap<String, String> implementations = new HashMap<String, String>();
implementations.put("exist", "net.xqj.exist.ExistXQDataSource");
implementations.put("oracle", "oracle.xquery.xqj.OXQDataSource");
implementations.put("saxon", "org.orbeon.saxon.xqj.SaxonXQDataSource");
return implementations;
}
private static HashMap<String, String> knownJDBCImplementations = initKnownJDBCImplementations();
private static HashMap<String, String> initKnownJDBCImplementations() {
HashMap<String, String> implementations = new HashMap<String, String>();
implementations.put("oracle", "oracle.jdbc.OracleDriver");
return implementations;
}
public XQueryProcessor() {
addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, XQUERY_NAMESPACE_URI));
addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA));
}
@Override
public ProcessorOutput createOutput(String name) {
final ProcessorOutput output = new ProcessorOutputImpl(XQueryProcessor.this, name) {
public void readImpl(final PipelineContext pipelineContext, XMLReceiver xmlReceiver) {
class ConfigContainer extends ObjectReceiver {
public ConfigContainer() {
}
public Config config;
class Config extends ObjectReceiver {
public Config() {
}
public String vendor;
public String implementation;
public String info;
public String username;
public String password;
public JDBC jdbc;
public Vector<NameValuePair> property = new Vector<NameValuePair>();
public String query;
public Vector<NameValuePair> parameter = new Vector<NameValuePair>();
class NameValuePair extends ObjectReceiver {
public NameValuePair() {
}
public String name;
public String value;
}
class JDBC extends ObjectReceiver {
public JDBC() {
}
public String url;
public String implementation;
}
String getImplementation() {
if (vendor != null) {
return knownImplementations.get(vendor);
}
return implementation;
}
public String getJDBCImplementation() {
if (jdbc.implementation != null) {
return jdbc.implementation;
}
return knownJDBCImplementations.get(vendor);
}
}
}
final ConfigContainer container = new ConfigContainer();
readInputAsSAX(pipelineContext, INPUT_CONFIG, container);
final ConfigContainer.Config config = container.config;
XMLReceiverHelper helper = new XMLReceiverHelper(xmlReceiver);
try {
if ("oracle".equals(config.vendor)) {
// Use JDBC as a workaround until we find out how to set the connection info in XQJ
// (see https://forums.oracle.com/forums/thread.jspa?messageID=10338407#10338407)
Driver driver = (Driver) Class.forName(config.getJDBCImplementation()).newInstance();
Connection conn = driver.connect(config.jdbc.url, null);
StringBuilder xquery = new StringBuilder("SELECT * from XMLTable('" + config.query.replaceAll("'", "''") + "' ");
Iterator<ConfigContainer.Config.NameValuePair> iter = config.parameter.iterator();
int i = 1;
while (iter.hasNext()) {
ConfigContainer.Config.NameValuePair parameter = iter.next();
xquery.append((i == 1 ? "PASSING " : ", ") + ":" + i + " AS \"" + parameter.name + "\" ");
i++;
}
xquery.append(")");
logger.debug("XQuery: " + xquery.toString());
PreparedStatement statement = conn.prepareStatement(xquery.toString());
i = 1;
iter = config.parameter.iterator();
while (iter.hasNext()) {
ConfigContainer.Config.NameValuePair parameter = iter.next();
statement.setString(i, parameter.value);
i++;
}
ResultSet rs = statement.executeQuery();
helper.startDocument();
helper.startElement("results");
while (rs.next()) {
for (i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
helper.startElement("result");
// This used to use SQLXML.getString() but "The behavior of this method is the same as
// ResultSet.getString() when the designated column of the ResultSet has a
// type java.sql.Types of SQLXML." For 1.5 compatibility we use getString() instead.
String sqlxml = rs.getString(i);
if (sqlxml != null) {
XMLParsing.parseDocumentFragment(sqlxml, xmlReceiver);
}
}
helper.endElement();
}
helper.endElement();
helper.endDocument();
rs.close();
statement.close();
conn.close();
} else {
// Use XQJ
XQDataSource xqs = (XQDataSource) Class.forName(config.getImplementation()).newInstance();
if (config.info != null) {
helper.startDocument();
helper.startElement("info");
helper.element("vendor", config.vendor == null ? "" : config.vendor);
helper.element("implementation", config.getImplementation());
Class[] interfaces = xqs.getClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
helper.element("implements", interfaces[i].getCanonicalName());
}
Constructor[] constructors = xqs.getClass().getConstructors();
for (int i = 0; i < constructors.length; i++) {
helper.element("constructor", constructors[i].toString());
}
Method[] methods = xqs.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
helper.element("method", methods[i].toString());
}
String[] names = xqs.getSupportedPropertyNames();
helper.startElement("supported-properties");
for (int i = 0; i < names.length; i++) {
helper.element("property", names[i]);
}
helper.endElement();
helper.endElement();
helper.endDocument();
} else {
Iterator<ConfigContainer.Config.NameValuePair> iter = config.property.iterator();
while (iter.hasNext()) {
ConfigContainer.Config.NameValuePair property = iter.next();
xqs.setProperty(property.name, property.value);
}
if (SaxonXQDataSource.class.isInstance(xqs)) {
// For Saxon: setup a URI resolver to support the "input:" scheme
final TransformerURIResolver resolver = new TransformerURIResolver(XQueryProcessor.this, pipelineContext, INPUT_CONFIG, XMLParsing.ParserConfiguration.PLAIN);
((SaxonXQDataSource) xqs).getConfiguration().setURIResolver(resolver);
}
XQConnection conn;
if (config.jdbc != null) {
Driver driver = (Driver) Class.forName(config.getJDBCImplementation()).newInstance();
Connection jdbcConn = driver.connect(config.jdbc.url, null);
// Class.forName(config.getJDBCImplementation());
// Connection jdbcConn = DriverManager.getConnection(config.jdbc.url);
conn = xqs.getConnection(jdbcConn);
} else if (config.username != null) {
conn = xqs.getConnection(config.username, config.password);
} else {
conn = xqs.getConnection();
}
XQPreparedExpression xqpe = conn.prepareExpression(config.query);
iter = config.parameter.iterator();
while (iter.hasNext()) {
ConfigContainer.Config.NameValuePair parameter = iter.next();
xqpe.bindString(new QName(parameter.name), parameter.value, null);
}
XQResultSequence rs = xqpe.executeQuery();
helper.startDocument();
helper.startElement("results");
while (rs.next()) {
helper.startElement("result");
if (rs.getItemType().getItemKind() == XQItemType.XQITEMKIND_TEXT || rs.getItemType().getItemKind() == XQItemType.XQITEMKIND_ATOMIC) {
helper.text(rs.getItem().getAtomicValue());
} else {
rs.writeItemToSAX(new SimpleForwardingXMLReceiver(xmlReceiver) {
public void startDocument() throws SAXException {
}
public void endDocument() throws SAXException {
}
});
}
helper.endElement();
}
helper.endElement();
helper.endDocument();
conn.close();
}
}
} catch (Exception e) {
throw new OXFException(e);
}
}
};
addOutput(name, output);
return output;
}
}