You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by th...@apache.org on 2011/09/15 14:53:42 UTC
svn commit: r1171083 [2/2] - in /cocoon/cocoon3/trunk: ./ cocoon-all/
cocoon-databases-sample/ cocoon-databases-sample/rcl-config/
cocoon-databases-sample/rcl-config/WEB-INF/
cocoon-databases-sample/rcl-config/WEB-INF/classes/
cocoon-databases-sample/s...
Added: cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java?rev=1171083&view=auto
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java (added)
+++ cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java Thu Sep 15 12:53:41 2011
@@ -0,0 +1,1658 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.sax.component;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Field;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.TreeMap;
+
+import javax.xml.parsers.SAXParser;
+
+import org.apache.cocoon.sax.SAXConsumer;
+import org.apache.cocoon.pipeline.components.parameters.Parameters;
+import org.apache.cocoon.sax.AbstractSAXTransformer;
+import org.apache.cocoon.sax.util.XMLUtils;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * The <code>SQLTransformer</code> can be plugged into a pipeline to transform
+ * SAX events into updated or queries and responses to/from a SQL interface.
+ *
+ * <p>
+ * It is declared and configured as follows:
+ *
+ * <pre>
+ * <map:transformers default="...">
+ * <map:transformer name="sql" src="org.apache.cocoon.transformation.SQLTransformer">
+ * <old-driver>false</old-driver>
+ * <connection-attempts>5</connection-attempts>
+ * <connection-waittime>5000</connection-waittime>
+ * </map:transformer>
+ * </map:transformers>
+ * </pre>
+ *
+ * </p>
+ *
+ * <p>
+ * It can be used in the sitemap pipeline as follows: <code>
+ * <map:transform type="sql">
+ * <!-- True to force each query to create its own connection: -->
+ * <map:parameter name="own-connection" value="..."/>
+ * <!-- Specify either name of datasource: -->
+ * <map:parameter name="use-connection" value="..."/>
+ * <!-- Or connection parameters: -->
+ * <map:parameter name="dburl" value="..."/>
+ * <map:parameter name="username" value="..."/>
+ * <map:parameter name="password" value="..."/>
+ *
+ * <!-- Default query parameters: -->
+ * <map:parameter name="show-nr-or-rows" value="false"/>
+ * <map:parameter name="doc-element" value="rowset"/>
+ * <map:parameter name="row-element" value="row"/>
+ * <map:parameter name="namespace-uri" value="http://apache.org/cocoon/SQL/2.0"/>
+ * <map:parameter name="namespace-prefix" value="sql"/>
+ * <map:parameter name="clob-encoding" value=""/>
+ * </map:transform>
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * The following DTD is valid: <code>
+ * <!ENTITY % param "(own-connection?,(use-connection|(dburl,username,password))?,show-nr-or-rows?,doc-element?,row-element?,namespace-uri?,namespace-prefix?,clob-encoding?)"><br>
+ * <!ELEMENT execute-query (query,(in-parameter|out-parameter)*,execute-query?, %param;)><br>
+ * <!ELEMENT own-connection (#PCDATA)><br>
+ * <!ELEMENT use-connection (#PCDATA)><br>
+ * <!ELEMENT query (#PCDATA | substitute-value | ancestor-value | escape-string)*><br>
+ * <!ATTLIST query name CDATA #IMPLIED isstoredprocedure (true|false) "false" isupdate (true|false) "false"><br>
+ * <!ELEMENT substitute-value EMPTY><br>
+ * <!ATTLIST substitute-value name CDATA #REQUIRED><br>
+ * <!ELEMENT ancestor-value EMPTY><br>
+ * <!ATTLIST ancestor-value name CDATA #REQUIRED level CDATA #REQUIRED><br>
+ * <!ELEMENT in-parameter EMPTY><br>
+ * <!ATTLIST in-parameter nr CDATA #REQUIRED type CDATA #REQUIRED><br>
+ * <!ELEMENT out-parameter EMPTY><br>
+ * <!ATTLIST out-parameter nr CDATA #REQUIRED name CDATA #REQUIRED type CDATA #REQUIRED><br>
+ * <!ELEMENT escape-string (#PCDATA)><br>
+ * </code>
+ * </p>
+ *
+ * <p>
+ * Each query can override default transformer parameters. Nested queries do not
+ * inherit parent query parameters, but only transformer parameters. Each query
+ * can have connection to different database, directly or using the connection
+ * pool. If database connection parameters are the same as for any of the
+ * ancestor queries, nested query will re-use ancestor query connection.
+ * </p>
+ *
+ * <p>
+ * Connection sharing between queries can be disabled, globally or on per-query
+ * basis, using <code>own-connection</code> parameter.
+ * </p>
+ *
+ * <p>
+ * By default, CLOBs are read from the database using character stream, so that
+ * character decoding is performed by the database. Using
+ * <code>clob-encoding</code> parameter, this behavior can be overrided, so that
+ * data is read as byte stream and decoded using specified character encoding.
+ * </p>
+ *
+ * <p>
+ * TODO: Support inserting of the XML data into the database without need to
+ * escape it. Can be implemented by introducing new <sql:xml/> tag to
+ * indicate that startSerializedXMLRecording(...) should be used.
+ * </p>
+ *
+ * @version $Id: SQLTransformer.java 587758 2007-10-24 02:55:27Z vgritsenko $
+ */
+public class SQLTransformer extends AbstractSAXTransformer implements
+ SAXConsumer {
+
+ private static final String CONNECT_WAITTIME = "connect-waittime";
+
+ public static final String CONNECT_ATTEMPTS = "connect-attempts";
+
+ private static final int BUFFER_SIZE = 1024;
+
+ /** The SQL transformer namespace */
+ public static final String NAMESPACE = "http://apache.org/cocoon/SQL/2.0";
+
+ // The SQL trasformer namespace element names
+ public static final String MAGIC_EXECUTE_QUERY = "execute-query";
+ private static final String MAGIC_OWN_CONNECTION = "own-connection";
+ public static final String MAGIC_CONNECTION = "use-connection";
+ public static final String MAGIC_DBURL = "dburl";
+ public static final String MAGIC_USERNAME = "username";
+ public static final String MAGIC_PASSWORD = "password";
+ public static final String MAGIC_NR_OF_ROWS = "show-nr-of-rows";
+ public static final String MAGIC_QUERY = "query";
+ public static final String MAGIC_VALUE = "value";
+ public static final String MAGIC_COLUMN_CASE = "column-case";
+ public static final String MAGIC_DOC_ELEMENT = "doc-element";
+ public static final String MAGIC_ROW_ELEMENT = "row-element";
+ public static final String MAGIC_IN_PARAMETER = "in-parameter";
+ public static final String MAGIC_IN_PARAMETER_NR_ATTRIBUTE = "nr";
+ public static final String MAGIC_IN_PARAMETER_VALUE_ATTRIBUTE = "value";
+ public static final String MAGIC_OUT_PARAMETER = "out-parameter";
+ public static final String MAGIC_OUT_PARAMETER_NAME_ATTRIBUTE = "name";
+ public static final String MAGIC_OUT_PARAMETER_NR_ATTRIBUTE = "nr";
+ public static final String MAGIC_OUT_PARAMETER_TYPE_ATTRIBUTE = "type";
+ public static final String MAGIC_ESCAPE_STRING = "escape-string";
+ public static final String MAGIC_ERROR = "error";
+
+ public static final String MAGIC_NS_URI_ELEMENT = "namespace-uri";
+ public static final String MAGIC_NS_PREFIX_ELEMENT = "namespace-prefix";
+
+ public static final String MAGIC_ANCESTOR_VALUE = "ancestor-value";
+ public static final String MAGIC_ANCESTOR_VALUE_LEVEL_ATTRIBUTE = "level";
+ public static final String MAGIC_ANCESTOR_VALUE_NAME_ATTRIBUTE = "name";
+ public static final String MAGIC_SUBSTITUTE_VALUE = "substitute-value";
+ public static final String MAGIC_SUBSTITUTE_VALUE_NAME_ATTRIBUTE = "name";
+ public static final String MAGIC_NAME_ATTRIBUTE = "name";
+ public static final String MAGIC_STORED_PROCEDURE_ATTRIBUTE = "isstoredprocedure";
+ public static final String MAGIC_UPDATE_ATTRIBUTE = "isupdate";
+ public static final String CLOB_ENCODING = "clob-encoding";
+
+ // The states we are allowed to be in
+ protected static final int STATE_OUTSIDE = 0;
+ protected static final int STATE_INSIDE_EXECUTE_QUERY_ELEMENT = 1;
+ protected static final int STATE_INSIDE_VALUE_ELEMENT = 2;
+ protected static final int STATE_INSIDE_QUERY_ELEMENT = 3;
+ protected static final int STATE_INSIDE_ANCESTOR_VALUE_ELEMENT = 4;
+ protected static final int STATE_INSIDE_SUBSTITUTE_VALUE_ELEMENT = 5;
+ protected static final int STATE_INSIDE_IN_PARAMETER_ELEMENT = 6;
+ protected static final int STATE_INSIDE_OUT_PARAMETER_ELEMENT = 7;
+ protected static final int STATE_INSIDE_ESCAPE_STRING = 8;
+
+ //
+ // Configuration
+ //
+
+ /** Is the old-driver turned on? (default is off) */
+ protected boolean oldDriver;
+
+ /** How many connection attempts to do? (default is 5 times) */
+ protected int connectAttempts;
+
+ /** How long wait between connection attempts? (default is 5000 ms) */
+ protected int connectWaittime;
+
+ //
+ // State
+ //
+
+ /** The current query we are working on */
+ protected Query query;
+
+ /** The current state of the event receiving FSM */
+ protected int state;
+
+ /**
+ * The "name" of the connection shared by top level queries (if
+ * configuration allows)
+ */
+ protected String connName;
+
+ /** The connection shared by top level queries (if configuration allows) */
+ protected Connection conn;
+
+ // Used to parse XML from database.
+ protected SAXParser parser;
+ /**
+ * The parameters specified in the sitemap
+ */
+ private Parameters parameters;
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory
+ .getLogger(SQLTransformer.class);
+
+ /**
+ * A stack for collecting information. The stack is important for collection
+ * information especially when the tags can be nested.
+ */
+ protected final Stack<String> stack = new Stack<String>();
+
+ @Autowired
+ private ApplicationContext context;
+
+ /**
+ * FIXME: Constructor
+ */
+ public SQLTransformer() {
+ this.ignoreHooksCount = 0;
+ this.ignoreEventsCount = 0;
+ }
+
+ // FIXME
+ // Lifecycle Methods
+ //
+ /**
+ * Recycle this component
+ */
+ public void recycle() {
+ this.query = null;
+ try {
+ // Close the connection used by all top level queries
+ if (this.conn != null) {
+ this.conn.close();
+ this.conn = null;
+ }
+ } catch (SQLException e) {
+ LOG.info("Could not close connection", e);
+ }
+ this.connName = null;
+ }
+
+ /**
+ * Configure transformer. Supported configuration elements:
+ * <ul>
+ * <li>old-driver</li>
+ * <li>connect-attempts</li>
+ * <li>connect-waittime</li>
+ * </ul>
+ */
+ @Override
+ public void setConfiguration(
+ final Map<String, ? extends Object> configuration) {
+ parameters = new Parameters(configuration);
+ this.oldDriver = parameters.getAsBoolean("old-driver", false);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Value for old-driver is " + this.oldDriver);
+ }
+
+ this.connectAttempts = parameters.getAsInteger(
+ SQLTransformer.CONNECT_ATTEMPTS, 5);
+ this.connectWaittime = parameters.getAsInteger(
+ SQLTransformer.CONNECT_WAITTIME, 5000);
+ // Setup instance variables
+ this.state = SQLTransformer.STATE_OUTSIDE;
+ this.connName = name(parameters);
+ }
+
+ /**
+ * Return attribute value. First try non-namespaced attribute, then try this
+ * transformer namespace.
+ *
+ * @param name
+ * local attribute name
+ */
+ private String getAttributeValue(Attributes attr, String name) {
+ String value = attr.getValue("", name);
+ if (value == null) {
+ value = attr.getValue(SQLTransformer.NAMESPACE, name);
+ }
+
+ return value;
+ }
+
+ /**
+ * Controlls SAX event handling. If this is incremented all events are not
+ * forwarded to the next pipeline component, but the hooks are still called.
+ */
+ protected int ignoreEventsCount;
+ /**
+ * Controlls SAX event handling. If this is greater than zero, the hooks are
+ * not called. Attention, make sure, that you decrement this counter
+ * properly as your hooks are not called anymore!
+ */
+ protected int ignoreHooksCount;
+
+ /**
+ * Process the SAX event. The namespace of the event is checked. If it is
+ * the defined namespace for this transformer, the
+ * {@link #startTransformingElement} hook is called.
+ *
+ * @see org.xml.sax.ContentHandler#startElement
+ */
+ public void startElement(String uri, String name, String raw,
+ Attributes attr) throws SAXException {
+ if (NAMESPACE.equals(uri) && ignoreHooksCount == 0) {
+ startTransformingElement(uri, name, raw, attr);
+ } else {
+ if (ignoreEventsCount == 0) {
+ this.getSAXConsumer().startElement(uri, name, raw, attr);
+ }
+ }
+ }
+
+ /**
+ * Process the SAX event. The namespace of the event is checked. If it is
+ * the defined namespace for this transformer, the
+ * {@link #endTransformingElement} hook is called.
+ *
+ * @see org.xml.sax.ContentHandler#endElement
+ */
+ public void endElement(String uri, String name, String raw)
+ throws SAXException {
+ if (NAMESPACE.equals(uri) && this.ignoreHooksCount == 0) {
+ // this is our namespace:
+ try {
+ endTransformingElement(uri, name, raw);
+ } catch (IOException e) {
+ throw new SAXException(
+ "IOException occured during processing: " + e, e);
+ }
+ } else {
+ if (ignoreEventsCount == 0) {
+ super.endElement(uri, name, raw);
+ }
+ }
+ }
+
+ //
+ // SAX Events Handlers
+ //
+
+ protected static void throwIllegalStateException(String message) {
+ throw new IllegalStateException("Illegal state: " + message);
+ }
+
+ /** <execute-query> */
+ protected void startExecuteQueryElement() {
+ switch (state) {
+ case SQLTransformer.STATE_OUTSIDE:
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ // Create root query (if query == null), or child query
+ this.query = new Query(this.query);
+ state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a start execute query element");
+ }
+ }
+
+ /** <*> */
+ protected void startValueElement(String name) throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ this.stack.push(name);
+ startTextRecording();
+ state = SQLTransformer.STATE_INSIDE_VALUE_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a start value element: "
+ + name);
+ }
+ }
+
+ /** <query> */
+ protected void startQueryElement(Attributes attributes) throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ startTextRecording();
+ state = SQLTransformer.STATE_INSIDE_QUERY_ELEMENT;
+
+ String isUpdate = attributes.getValue("",
+ SQLTransformer.MAGIC_UPDATE_ATTRIBUTE);
+ if (isUpdate != null && !isUpdate.equalsIgnoreCase("false")) {
+ query.setUpdate(true);
+ }
+
+ String isProcedure = attributes.getValue("",
+ SQLTransformer.MAGIC_STORED_PROCEDURE_ATTRIBUTE);
+ if (isProcedure != null && !isProcedure.equalsIgnoreCase("false")) {
+ query.setStoredProcedure(true);
+ }
+
+ String name = attributes.getValue("",
+ SQLTransformer.MAGIC_NAME_ATTRIBUTE);
+ if (name != null) {
+ query.setName(name);
+ }
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a start query element");
+ }
+ }
+
+ /** </query> */
+ protected void endQueryElement() throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_QUERY_ELEMENT:
+ final String value = endTextRecording();
+ if (value.length() > 0) {
+ query.addQueryPart(value);
+ }
+ state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a stop query element");
+ }
+ }
+
+ /** </*> */
+ protected void endValueElement() throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_VALUE_ELEMENT:
+ final String name = this.stack.pop();
+ final String value = endTextRecording();
+ query.setParameter(name, value);
+ this.state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting an end value element");
+ }
+ }
+
+ /** </execute-query> */
+ protected void endExecuteQueryElement() throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ if (query.parent == null) {
+ query.executeQuery();
+ query = null;
+ state = SQLTransformer.STATE_OUTSIDE;
+ } else {
+ query.parent.addNestedQuery(query);
+ query = query.parent;
+ state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ }
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting an end execute query element");
+ }
+ }
+
+ /** <ancestor-value> */
+ protected void startAncestorValueElement(Attributes attributes)
+ throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_QUERY_ELEMENT:
+ int level = 0;
+ try {
+ level = Integer.parseInt(getAttributeValue(attributes,
+ SQLTransformer.MAGIC_ANCESTOR_VALUE_LEVEL_ATTRIBUTE));
+ } catch (Exception e) {
+ LOG.debug("Invalid or missing value for "
+ + SQLTransformer.MAGIC_ANCESTOR_VALUE_LEVEL_ATTRIBUTE
+ + " attribute", e);
+ throwIllegalStateException("Ancestor value elements must have a "
+ + SQLTransformer.MAGIC_ANCESTOR_VALUE_LEVEL_ATTRIBUTE
+ + " attribute");
+ }
+
+ String name = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_ANCESTOR_VALUE_NAME_ATTRIBUTE);
+ if (name == null) {
+ throwIllegalStateException("Ancestor value elements must have a "
+ + SQLTransformer.MAGIC_ANCESTOR_VALUE_NAME_ATTRIBUTE
+ + " attribute");
+ }
+
+ final String value = endTextRecording();
+ if (value.length() > 0) {
+ query.addQueryPart(value);
+ }
+ query.addQueryPart(new AncestorValue(level, name));
+ startTextRecording();
+
+ state = SQLTransformer.STATE_INSIDE_ANCESTOR_VALUE_ELEMENT;
+ break;
+ default:
+ throwIllegalStateException("Not expecting a start ancestor value element");
+ }
+ }
+
+ /** </ancestor-value> */
+ protected void endAncestorValueElement() {
+ state = SQLTransformer.STATE_INSIDE_QUERY_ELEMENT;
+ }
+
+ /** <substitute-value> */
+ protected void startSubstituteValueElement(Attributes attributes)
+ throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_QUERY_ELEMENT:
+ String name = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_SUBSTITUTE_VALUE_NAME_ATTRIBUTE);
+ if (name == null) {
+ throwIllegalStateException("Substitute value elements must have a "
+ + SQLTransformer.MAGIC_SUBSTITUTE_VALUE_NAME_ATTRIBUTE
+ + " attribute");
+ }
+ String substitute = (String) parameters.getParameter(name, null);
+ // Escape single quote
+ substitute = escapeSql(substitute);
+ final String value = endTextRecording();
+ if (value.length() > 0) {
+ query.addQueryPart(value);
+ }
+ query.addQueryPart(substitute);
+ startTextRecording();
+
+ state = SQLTransformer.STATE_INSIDE_SUBSTITUTE_VALUE_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a start substitute value element");
+ }
+ }
+
+ private String escapeSql(String substitute) {
+ if (null != substitute) {
+ substitute = StringUtils.replace(substitute, "'", "''");
+ }
+ return substitute;
+ }
+
+ /** </substitute-value> */
+ protected void endSubstituteValueElement() {
+ state = SQLTransformer.STATE_INSIDE_QUERY_ELEMENT;
+ }
+
+ /** <escape-string> */
+ protected void startEscapeStringElement(Attributes attributes)
+ throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_QUERY_ELEMENT:
+ final String value = endTextRecording();
+ if (value.length() > 0) {
+ query.addQueryPart(value);
+ }
+ startTextRecording();
+
+ state = SQLTransformer.STATE_INSIDE_ESCAPE_STRING;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a start escape-string element");
+ }
+ }
+
+ /** </escape-string> */
+ protected void endEscapeStringElement() throws SAXException {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_ESCAPE_STRING:
+ String value = endTextRecording();
+ if (value.length() > 0) {
+ value = escapeSql(value);
+ value = StringUtils.replace(value, "\\", "\\\\");
+ query.addQueryPart(value);
+ }
+ startTextRecording();
+ state = SQLTransformer.STATE_INSIDE_QUERY_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting a end escape-string element");
+ }
+ }
+
+ /** <in-parameter> */
+ protected void startInParameterElement(Attributes attributes) {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ String nr = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_IN_PARAMETER_NR_ATTRIBUTE);
+ String value = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_IN_PARAMETER_VALUE_ATTRIBUTE);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("IN PARAMETER NR " + nr + "; VALUE " + value);
+ }
+
+ int position = Integer.parseInt(nr);
+ query.setInParameter(position, value);
+ state = SQLTransformer.STATE_INSIDE_IN_PARAMETER_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting an in-parameter element");
+ }
+ }
+
+ /** </in-parameter> */
+ protected void endInParameterElement() {
+ state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ }
+
+ /** <out-parameter> */
+ protected void startOutParameterElement(Attributes attributes) {
+ switch (state) {
+ case SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT:
+ String name = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_OUT_PARAMETER_NAME_ATTRIBUTE);
+ String nr = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_OUT_PARAMETER_NR_ATTRIBUTE);
+ String type = getAttributeValue(attributes,
+ SQLTransformer.MAGIC_OUT_PARAMETER_TYPE_ATTRIBUTE);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("OUT PARAMETER NAME" + name + ";NR " + nr + "; TYPE "
+ + type);
+ }
+
+ int position = Integer.parseInt(nr);
+ query.setOutParameter(position, type, name);
+ state = SQLTransformer.STATE_INSIDE_OUT_PARAMETER_ELEMENT;
+ break;
+
+ default:
+ throwIllegalStateException("Not expecting an out-parameter element");
+ }
+ }
+
+ /** </out-parameter> */
+ protected void endOutParameterElement() {
+ state = SQLTransformer.STATE_INSIDE_EXECUTE_QUERY_ELEMENT;
+ }
+
+ /**
+ * ContentHandler method
+ */
+ public void startTransformingElement(String uri, String name, String raw,
+ Attributes attributes) throws SAXException {
+ if (name.equals(SQLTransformer.MAGIC_EXECUTE_QUERY)) {
+ startExecuteQueryElement();
+ } else if (name.equals(SQLTransformer.MAGIC_QUERY)) {
+ startQueryElement(attributes);
+ } else if (name.equals(SQLTransformer.MAGIC_ANCESTOR_VALUE)) {
+ startAncestorValueElement(attributes);
+ } else if (name.equals(SQLTransformer.MAGIC_SUBSTITUTE_VALUE)) {
+ startSubstituteValueElement(attributes);
+ } else if (name.equals(SQLTransformer.MAGIC_IN_PARAMETER)) {
+ startInParameterElement(attributes);
+ } else if (name.equals(SQLTransformer.MAGIC_OUT_PARAMETER)) {
+ startOutParameterElement(attributes);
+ } else if (name.equals(SQLTransformer.MAGIC_ESCAPE_STRING)) {
+ startEscapeStringElement(attributes);
+ } else {
+ startValueElement(name);
+ }
+ }
+
+ /**
+ * ContentHandler method
+ */
+ public void endTransformingElement(String uri, String name, String raw)
+ throws IOException, SAXException {
+ if (name.equals(SQLTransformer.MAGIC_EXECUTE_QUERY)) {
+ endExecuteQueryElement();
+ } else if (name.equals(SQLTransformer.MAGIC_QUERY)) {
+ endQueryElement();
+ } else if (name.equals(SQLTransformer.MAGIC_ANCESTOR_VALUE)) {
+ endAncestorValueElement();
+ } else if (name.equals(SQLTransformer.MAGIC_SUBSTITUTE_VALUE)) {
+ endSubstituteValueElement();
+ } else if (name.equals(SQLTransformer.MAGIC_IN_PARAMETER)) {
+ endInParameterElement();
+ } else if (name.equals(SQLTransformer.MAGIC_OUT_PARAMETER)) {
+ endOutParameterElement();
+ } else if (name.equals(SQLTransformer.MAGIC_ESCAPE_STRING)) {
+ endEscapeStringElement();
+ } else {
+ endValueElement();
+ }
+ }
+
+ //
+ // Helper methods for the Query
+ //
+
+ /**
+ * Qualifies an element name by giving it a prefix.
+ *
+ * @param name
+ * the element name
+ * @param prefix
+ * the prefix to qualify with
+ * @return a namespace qualified name that is correct
+ */
+ protected String nsQualify(String name, String prefix) {
+ if (StringUtils.isEmpty(name)) {
+ return name;
+ }
+
+ if (StringUtils.isNotEmpty(prefix)) {
+ return prefix + ":" + name;
+ }
+
+ return name;
+ }
+
+ /**
+ * Helper method for generating SAX events
+ */
+ protected void start(String uri, String prefix, String name, Attributes attr)
+ throws SAXException {
+ try {
+ this.getSAXConsumer().startElement(uri, name, nsQualify(name, prefix), attr);
+ } catch (Exception e) {
+ throw new SAXException(e);
+ }
+ }
+
+ /**
+ * Helper method for generating SAX events
+ */
+ protected void end(String uri, String prefix, String name)
+ throws SAXException {
+ try {
+ super.endElement(uri, name, nsQualify(name, prefix));
+ } catch (Exception e) {
+ throw new SAXException(e);
+ }
+ }
+
+ /**
+ * Helper method for generating SAX events
+ */
+ protected void data(String data) throws SAXException {
+ if (data != null) {
+ super.characters(data.toCharArray(), 0, data.length());
+ }
+ }
+
+ /**
+ * Get 'name' for the connection which can be obtained using provided
+ * connection parameters.
+ */
+ private String name(Parameters params) {
+ final boolean ownConnection = params.getParameterAsBoolean(
+ SQLTransformer.MAGIC_OWN_CONNECTION, false);
+ if (ownConnection) {
+ return null;
+ }
+
+ final String datasourceName = (String) params.getParameter(
+ SQLTransformer.MAGIC_CONNECTION, null);
+ if (datasourceName != null) {
+ return "ds:" + datasourceName;
+ }
+
+ final String dburl = (String) params.getParameter(SQLTransformer.MAGIC_DBURL,
+ null);
+ if (dburl != null) {
+ final String username = (String) params.getParameter(
+ SQLTransformer.MAGIC_USERNAME, null);
+ final String password = (String) params.getParameter(
+ SQLTransformer.MAGIC_PASSWORD, null);
+
+ if (username == null || password == null) {
+ return "db:@" + dburl;
+ } else {
+ return "db:" + username + ":" + password + "@" + dburl;
+ }
+ }
+
+ // Nothing configured
+ return "";
+ }
+
+ /**
+ * Open database connection using provided parameters. Return null if
+ * neither datasource nor jndi URL configured.
+ */
+ private Connection open(Parameters params) throws SQLException {
+ Connection result = null;
+
+ // First check datasource name parameter
+ final String datasourceName = (String) params.getParameter(
+ SQLTransformer.MAGIC_CONNECTION, null);
+ if (datasourceName != null) {
+ BasicDataSource datasource = null;
+ try {
+ datasource = (BasicDataSource) this.context
+ .getBean(datasourceName);
+ for (int i = 0; i < this.connectAttempts && result == null; i++) {
+ try {
+ result = datasource.getConnection();
+ } catch (SQLException e) {
+ if (i + 1 < this.connectAttempts) {
+ final long waittime = this.connectWaittime;
+ // Log exception if debug enabled.
+ if (LOG.isDebugEnabled()) {
+ LOG.info("Unable to get connection; waiting "
+ + waittime + "ms to try again.", e);
+ } else {
+ LOG.info("Unable to get connection; waiting "
+ + waittime + "ms to try again.");
+ }
+ try {
+ Thread.sleep(waittime);
+ } catch (InterruptedException ex) {
+ /* ignored */
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new SQLException(
+ "Unable to get connection from datasource '"
+ + datasourceName + "': "
+ + "No such datasource.");
+ } finally {
+ // this.manager.release(datasource);
+ // FIXME: RElease db
+ }
+
+ if (result == null) {
+ throw new SQLException(
+ "Failed to obtain connection from datasource '"
+ + datasourceName + "'. " + "Made "
+ + this.connectAttempts + " attempts with "
+ + this.connectWaittime + "ms interval");
+ }
+ } else {
+ // Then, check connection URL parameter
+ final String dburl = (String) params.getParameter(
+ SQLTransformer.MAGIC_DBURL, null);
+ if (dburl != null) {
+ final String username = (String) params.getParameter(
+ SQLTransformer.MAGIC_USERNAME, null);
+ final String password = (String) params.getParameter(
+ SQLTransformer.MAGIC_PASSWORD, null);
+
+ if (username == null || password == null) {
+ result = DriverManager.getConnection(dburl);
+ } else {
+ result = DriverManager.getConnection(dburl, username,
+ password);
+ }
+ } else {
+ // Nothing configured
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * FIXME Attempt to parse string value
+ */
+ private void stream(String value) throws SAXException, IOException {
+ // Strip off the XML Declaration if there is one!
+ if (value.startsWith("<?xml ")) {
+ value = value.substring(value.indexOf("?>") + 2);
+ }
+ value = "<root>" + value + "</root>";
+ XMLUtils.toSax(value, this);
+ }
+
+ /**
+ * One of the queries in the query tree formed from nested queries.
+ */
+ private class Query {
+
+ /** Parent query, or null for top level query */
+ protected Query parent;
+
+ /** Nested sub-queries we have. */
+ protected final List<Query> nested = new ArrayList<Query>();
+
+ /** The parts of the query */
+ protected final List<Object> parts = new ArrayList<Object>();
+
+ //
+ // Query Configuration
+ //
+
+ /** Name of the query */
+ protected String name;
+
+ /** If this query is actually an update (insert, update, delete) */
+ protected boolean isUpdate;
+
+ /** If this query is actually a stored procedure */
+ protected boolean isStoredProcedure;
+
+ /** Query configuration parameters */
+ protected Parameters params;
+
+ /**
+ * The namespace uri of the XML output. Defaults to
+ * {@link SQLTransformer#NAMESPACE}.
+ */
+ protected String outUri;
+
+ /** The namespace prefix of the XML output. Defaults to 'sql'. */
+ protected String outPrefix;
+
+ /** rowset element name */
+ protected String rowsetElement;
+
+ /** row element name */
+ protected String rowElement;
+
+ /** number of rows attribute name */
+ protected String nrOfRowsAttr = "nrofrows";
+
+ /** Query name attribute name */
+ protected String nameAttr = "name";
+
+ /** Handling of case of column names in results */
+ protected int columnCase;
+
+ /** Registered IN parameters */
+ protected Map<Integer, String> inParameters;
+
+ /** Registered OUT parameters */
+ protected Map<Integer, String> outParameters;
+
+ /** Mapping out parameters - objectModel */
+ protected Map<Integer, String> outParametersNames;
+
+ /** Check if nr of rows need to be written out. */
+ protected boolean showNrOfRows;
+
+ /** Encoding we use for CLOB field */
+ protected String clobEncoding;
+
+ //
+ // Query State
+ //
+
+ /** The connection */
+ protected Connection conn;
+
+ /** The 'name' of the connection */
+ protected String connName;
+
+ /** Is it our own connection? */
+ protected boolean ownConn;
+
+ /** Prepared statement */
+ protected PreparedStatement pst;
+
+ /** Callable statement */
+ protected CallableStatement cst;
+
+ /** The results, of course */
+ protected ResultSet rs;
+
+ /** And the results' metadata */
+ protected ResultSetMetaData md;
+
+ /** If it is an update/etc, the return value (num rows modified) */
+ protected int rv = -1;
+
+ protected Query(Query parent) {
+ this.parent = parent;
+ this.params = new Parameters(new HashMap<String, Object>());
+ this.params.merge(SQLTransformer.this.parameters);
+ }
+
+ /** Add nested sub-query. */
+ protected void addNestedQuery(Query query) {
+ nested.add(query);
+ }
+
+ protected void addQueryPart(Object value) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding query part \"" + value + "\"");
+ }
+ parts.add(value);
+ }
+
+ protected String getName() {
+ return name;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ protected void setParameter(String name, String value) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding parameter name {" + name + "} value {"
+ + value + "}");
+ }
+ params.setParameter(name, value);
+ }
+
+ protected void setUpdate(boolean flag) {
+ isUpdate = flag;
+ }
+
+ protected void setStoredProcedure(boolean flag) {
+ isStoredProcedure = flag;
+ }
+
+ protected void setInParameter(int pos, String val) {
+ if (inParameters == null) {
+ inParameters = new HashMap<Integer, String>();
+ }
+ inParameters.put(new Integer(pos), val);
+ }
+
+ protected void setOutParameter(int pos, String type, String name) {
+ if (outParameters == null) {
+ // make sure output parameters are ordered
+ outParameters = new TreeMap<Integer, String>();
+ outParametersNames = new HashMap<Integer, String>();
+ }
+ outParameters.put(new Integer(pos), type);
+ outParametersNames.put(new Integer(pos), name);
+ }
+
+ private void setColumnCase(String columnCase) {
+ if (columnCase.equals("lowercase")) {
+ this.columnCase = -1;
+ } else if (columnCase.equals("uppercase")) {
+ this.columnCase = +1;
+ } else if (columnCase.equals("preserve")) {
+ // Do nothing
+ this.columnCase = 0;
+ } else {
+ LOG.warn("[" + columnCase
+ + "] is not a valid value for <column-case>. "
+ + "Column name retrieved from database will be used.");
+ }
+ }
+
+ private void registerInParameters() throws SQLException {
+ if (inParameters == null) {
+ return;
+ }
+
+ Iterator<Integer> i = inParameters.keySet().iterator();
+ while (i.hasNext()) {
+ Integer counter = i.next();
+ String value = inParameters.get(counter);
+ try {
+ pst.setObject(counter.intValue(), value);
+ } catch (SQLException e) {
+ LOG.error("Caught a SQLException", e);
+ throw e;
+ }
+ }
+ }
+
+ private void registerOutParameters(CallableStatement cst)
+ throws SQLException {
+ if (outParameters == null) {
+ return;
+ }
+
+ Iterator<Integer> i = outParameters.keySet().iterator();
+ while (i.hasNext()) {
+ Integer counter = i.next();
+ String type = outParameters.get(counter);
+
+ int index = type.lastIndexOf(".");
+ if (index == -1) {
+ LOG.error("Invalid SQLType: " + type);
+ throw new SQLException("Invalid SQLType: " + type);
+ }
+
+ String className = type.substring(0, index);
+ String fieldName = type.substring(index + 1, type.length());
+ try {
+ Class<?> clss = Class.forName(className);
+ Field fld = clss.getField(fieldName);
+ cst.registerOutParameter(counter.intValue(),
+ fld.getInt(fieldName));
+ } catch (Exception e) {
+ // Lots of different exceptions to catch
+ LOG.error(
+ "Invalid SQLType: " + className + "." + fieldName,
+ e);
+ }
+ }
+ }
+
+ /**
+ * Open database connection
+ */
+ private void open() throws SQLException {
+ this.connName = SQLTransformer.this.name(this.params);
+
+ // Check first if connection sharing disabled
+ if (this.connName == null) {
+ this.conn = SQLTransformer.this.open(this.params);
+ this.ownConn = true;
+ return;
+ }
+
+ // Iterate through parent queries and get appropriate connection
+ Query query = this.parent;
+ while (query != null) {
+ if (this.connName.equals(query.connName)) {
+ this.conn = query.conn;
+ this.ownConn = false;
+ return;
+ }
+ query = query.parent;
+ }
+
+ // Check 'global' connection
+ if (this.connName.equals(SQLTransformer.this.connName)) {
+ // Use SQLTransformer configuration: it has same connection
+ // parameters
+ if (SQLTransformer.this.conn == null) {
+ SQLTransformer.this.conn = SQLTransformer.this
+ .open(SQLTransformer.this.parameters);
+ }
+
+ this.conn = SQLTransformer.this.conn;
+ this.ownConn = false;
+ return;
+ }
+
+ // Create own connection
+ this.conn = SQLTransformer.this.open(this.params);
+ this.ownConn = true;
+ }
+
+ /**
+ * This will be the meat of SQLTransformer, where the query is run.
+ */
+ protected void executeQuery() throws SAXException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Executing query " + this);
+ }
+
+ this.outUri = (String) this.params.getParameter(
+ SQLTransformer.MAGIC_NS_URI_ELEMENT,
+ SQLTransformer.NAMESPACE);
+ this.outPrefix = (String) this.params.getParameter(
+ SQLTransformer.MAGIC_NS_PREFIX_ELEMENT, "sql");
+ this.rowsetElement = (String) this.params.getParameter(
+ SQLTransformer.MAGIC_DOC_ELEMENT, "rowset");
+ this.rowElement = (String) this.params.getParameter(
+ SQLTransformer.MAGIC_ROW_ELEMENT, "row");
+
+ this.showNrOfRows = parameters.getParameterAsBoolean(
+ SQLTransformer.MAGIC_NR_OF_ROWS, false);
+ this.clobEncoding = (String) parameters.getParameter(
+ SQLTransformer.CLOB_ENCODING, "");
+ if (this.clobEncoding.length() == 0) {
+ this.clobEncoding = null;
+ }
+
+ // Start prefix mapping for output namespace, only if it's not
+ // mapped yet
+ final String prefix = SQLTransformer.this
+ .findPrefixMapping(this.outUri);
+ if (prefix == null) {
+ SQLTransformer.this.startPrefixMapping(this.outPrefix,
+ this.outUri);
+ } else {
+ this.outPrefix = prefix;
+ }
+
+ boolean success = false;
+ try {
+ try {
+ open();
+ execute();
+ success = true;
+ } catch (SQLException e) {
+ LOG.info("Failed to execute query " + this, e);
+ start(this.rowsetElement, XMLUtils.EMPTY_ATTRIBUTES);
+ start(MAGIC_ERROR, XMLUtils.EMPTY_ATTRIBUTES);
+ data(e.getMessage());
+ end(MAGIC_ERROR);
+ end(this.rowsetElement);
+ }
+
+ if (success) {
+ AttributesImpl attr = new AttributesImpl();
+ if (showNrOfRows) {
+ attr.addAttribute("", this.nrOfRowsAttr,
+ this.nrOfRowsAttr, "CDATA",
+ String.valueOf(getNrOfRows()));
+ }
+ String name = getName();
+ if (name != null) {
+ attr.addAttribute("", this.nameAttr, this.nameAttr,
+ "CDATA", name);
+ }
+ start(this.rowsetElement, attr);
+
+ // Serialize stored procedure output parameters
+ if (isStoredProcedure) {
+ serializeStoredProcedure();
+ }
+
+ // Serialize result set
+ while (next()) {
+ start(this.rowElement, XMLUtils.EMPTY_ATTRIBUTES);
+ serializeRow();
+ for (Iterator<Query> i = this.nested.iterator(); i
+ .hasNext();) {
+ i.next().executeQuery();
+ }
+ end(this.rowElement);
+ }
+
+ end(this.rowsetElement);
+ }
+ } catch (SQLException e) {
+ LOG.debug("Exception in executeQuery()", e);
+ throw new SAXException(e);
+ } finally {
+ close();
+ }
+
+ if (prefix == null) {
+ SQLTransformer.this.endPrefixMapping(this.outPrefix);
+ }
+ }
+
+ /**
+ * Execute the query. Connection must be set already.
+ */
+ private void execute() throws SQLException {
+ setColumnCase((String) params.getParameter(SQLTransformer.MAGIC_COLUMN_CASE,
+ "lowercase"));
+
+ // Construct query string
+ StringBuffer sb = new StringBuffer();
+ for (Iterator<Object> i = parts.iterator(); i.hasNext();) {
+ Object object = i.next();
+ if (object instanceof String) {
+ sb.append((String) object);
+ } else if (object instanceof AncestorValue) {
+ // Do a lookup into the ancestors' result's values
+ AncestorValue av = (AncestorValue) object;
+ Query query = this;
+ for (int k = av.level; k > 0; k--) {
+ query = query.parent;
+ }
+ sb.append(query.getColumnValue(av.name));
+ }
+ }
+
+ String query = StringUtils.replace(sb.toString().trim(), "\r", " ",
+ -1);
+ // Test, if this is an update (by comparing with select)
+ if (!isStoredProcedure && !isUpdate) {
+ if (query.length() > 6
+ && !query.substring(0, 6).equalsIgnoreCase("SELECT")) {
+ isUpdate = true;
+ }
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Executing " + query);
+ }
+ if (!isStoredProcedure) {
+ if (oldDriver) {
+ pst = conn.prepareStatement(query);
+ } else {
+ pst = conn.prepareStatement(query,
+ ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY);
+ }
+ } else {
+ if (oldDriver) {
+ cst = conn.prepareCall(query);
+ } else {
+ cst = conn.prepareCall(query,
+ ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY);
+ }
+ registerOutParameters(cst);
+ pst = cst;
+ }
+
+ registerInParameters();
+ boolean result = pst.execute();
+ if (result) {
+ rs = pst.getResultSet();
+ md = rs.getMetaData();
+ } else {
+ rv = pst.getUpdateCount();
+ }
+ }
+
+ protected int getNrOfRows() throws SQLException {
+ int nr = 0;
+
+ if (rs != null) {
+ if (oldDriver) {
+ nr = -1;
+ } else {
+ try {
+ rs.last();
+ nr = rs.getRow();
+ rs.beforeFirst();
+ } catch (NullPointerException e) {
+ // A NullPointerException here crashes a whole lot of C2
+ // --
+ // catching it so it won't do any harm for now, but
+ // seems like it should be solved seriously
+ LOG.error("NPE while getting the nr of rows", e);
+ }
+ }
+ } else {
+ if (outParameters != null) {
+ nr = outParameters.size();
+ }
+ }
+ return nr;
+ }
+
+ protected String getColumnValue(ResultSet rs, int i)
+ throws SQLException {
+ final int type = rs.getMetaData().getColumnType(i);
+ if (type == java.sql.Types.DOUBLE) {
+ return getStringValue(rs.getBigDecimal(i));
+ } else if (type == java.sql.Types.CLOB) {
+ return getStringValue(rs.getClob(i));
+ } else {
+ return getStringValue(rs.getObject(i));
+ }
+ }
+
+ // fix not applied here because there is no metadata from Name -> number
+ // and coltype
+ // for a given "name" versus number. That being said this shouldn't be
+ // an issue
+ // as this function is only called for ancestor lookups.
+ protected String getColumnValue(String name) throws SQLException {
+ // noinspection UnnecessaryLocalVariable
+ String retval = getStringValue(rs.getObject(name));
+ // if (rs.getMetaData().getColumnType( name ) ==
+ // java.sql.Types.DOUBLE)
+ // retval = transformer.getStringValue( rs.getBigDecimal( name ) );
+ return retval;
+ }
+
+ protected boolean next() throws SQLException {
+ // If rv is not -1, then an SQL insert, update, etc, has
+ // happened (see JDBC docs - return codes for executeUpdate)
+ if (rv != -1) {
+ // Output row with return code. Once.
+ return true;
+ }
+
+ if (rs != null && rs.next()) {
+ // Have next row
+ return true;
+ }
+
+ while (pst.getMoreResults()) {
+ rs = pst.getResultSet();
+ md = rs.getMetaData();
+ if (rs.next()) {
+ // Have next row in next result set
+ return true;
+ }
+ }
+
+ // Nothing left
+ return false;
+ }
+
+ /**
+ * Closes all the resources, ignores (but logs) exceptions.
+ */
+ protected void close() {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ LOG.info("Unable to close the result set.", e);
+ }
+ // This prevents us from using the resultset again.
+ rs = null;
+ }
+
+ if (pst != null && pst != cst) {
+ try {
+ pst.close();
+ } catch (SQLException e) {
+ LOG.info("Unable to close the statement.", e);
+ }
+ }
+ // Prevent using pst again.
+ pst = null;
+
+ if (cst != null) {
+ try {
+ cst.close();
+ } catch (SQLException e) {
+ LOG.info("Unable to close the statement.", e);
+ }
+ // Prevent using cst again.
+ cst = null;
+ }
+
+ try {
+ if (ownConn && conn != null) {
+ conn.close();
+ }
+ } catch (SQLException e) {
+ LOG.info("Unable to close the connection", e);
+ }
+ // Prevent using conn again.
+ conn = null;
+ }
+
+ protected void serializeData(String value) throws SQLException,
+ SAXException {
+ if (value != null) {
+ value = value.trim();
+ // Could this be XML ?
+ if (value.length() > 0 && value.charAt(0) == '<') {
+ try {
+ // FIXME: IMPLEMENT
+ stream(value);
+ } catch (Exception ignored) {
+ // FIXME: bad coding "catch(Exception)"
+ // If an exception occured the data was not (valid) xml
+ data(value);
+ }
+ } else {
+ data(value);
+ }
+ }
+ }
+
+ protected void serializeRow() throws SQLException, SAXException {
+ if (rv != -1) {
+ start("returncode", XMLUtils.EMPTY_ATTRIBUTES);
+ serializeData(String.valueOf(rv));
+ end("returncode");
+ // We only want the return code shown once.
+ // Reset rv so next() returns false next time.
+ rv = -1;
+ } else {
+ for (int i = 1; i <= md.getColumnCount(); i++) {
+ String columnName = getColumnName(md.getColumnName(i));
+ start(columnName, XMLUtils.EMPTY_ATTRIBUTES);
+ serializeData(getColumnValue(rs, i));
+ end(columnName);
+ }
+ }
+ }
+
+ private void serializeResultSet(ResultSet rs) throws SQLException,
+ SAXException {
+ final ResultSetMetaData md = rs.getMetaData();
+ final int n = md.getColumnCount();
+
+ // Get column names
+ final String[] columns = new String[n + 1];
+ for (int i = 1; i <= n; i++) {
+ columns[i] = getColumnName(md.getColumnName(i));
+ }
+
+ // Process rows
+ while (rs.next()) {
+ start(rowElement, XMLUtils.EMPTY_ATTRIBUTES);
+ for (int i = 1; i <= n; i++) {
+ start(columns[i], XMLUtils.EMPTY_ATTRIBUTES);
+ serializeData(getColumnValue(rs, i));
+ end(columns[i]);
+ }
+ end(this.rowElement);
+ }
+ }
+
+ protected void serializeStoredProcedure() throws SQLException,
+ SAXException {
+ if (outParametersNames == null || cst == null) {
+ return;
+ }
+
+ Iterator<Integer> itOutKeys = outParameters.keySet().iterator();
+ while (itOutKeys.hasNext()) {
+ final Integer counter = itOutKeys.next();
+ try {
+ final Object obj = cst.getObject(counter.intValue());
+ final String name = outParametersNames.get(counter);
+ start(name, XMLUtils.EMPTY_ATTRIBUTES);
+
+ if (!(obj instanceof ResultSet)) {
+ serializeData(getStringValue(obj));
+ } else {
+ final ResultSet rs = (ResultSet) obj;
+ try {
+ serializeResultSet(rs);
+ } finally {
+ try {
+ rs.close();
+ } catch (SQLException e) { /* ignored */
+ }
+ }
+ }
+
+ end(name);
+ } catch (SQLException e) {
+ LOG.error("Caught a SQLException", e);
+ throw e;
+ }
+ }
+ }
+
+ private String getColumnName(String columnName) {
+ switch (this.columnCase) {
+ case -1:
+ columnName = columnName.toLowerCase();
+ break;
+ case +1:
+ columnName = columnName.toUpperCase();
+ break;
+ default:
+ // Do nothing
+ }
+ return columnName;
+ }
+
+ /**
+ * Convert object to string represenation
+ */
+ private String getStringValue(Object object) throws SQLException {
+ if (object instanceof byte[]) {
+ // FIXME Encoding?
+ return new String((byte[]) object);
+ }
+
+ if (object instanceof char[]) {
+ return new String((char[]) object);
+ }
+
+ // Old behavior: Read bytes & decode
+ if (object instanceof Clob && this.clobEncoding != null) {
+ Clob clob = (Clob) object;
+ StringBuffer buffer = new StringBuffer();
+ InputStream is = clob.getAsciiStream();
+ try {
+ byte[] bytes = new byte[BUFFER_SIZE];
+ int n;
+ while ((n = is.read(bytes)) > -1) {
+ buffer.append(new String(bytes, 0, n, this.clobEncoding));
+ }
+ } catch (IOException e) {
+ throw new SQLException("Error reading stream from CLOB");
+ }
+ return buffer.toString();
+ }
+
+ // Correct behavior: Read character data
+ if (object instanceof Clob) {
+ Clob clob = (Clob) object;
+ StringBuffer buffer = new StringBuffer();
+ Reader cs = clob.getCharacterStream();
+ try {
+ char[] chars = new char[BUFFER_SIZE];
+ int n;
+ while ((n = cs.read(chars)) > -1) {
+ buffer.append(chars, 0, n);
+ }
+ } catch (IOException e) {
+ throw new SQLException("Error reading stream from CLOB");
+ }
+ return buffer.toString();
+ }
+
+ if (object != null) {
+ return object.toString();
+ }
+
+ return "";
+ }
+
+ private void start(String name, Attributes attr) throws SAXException {
+ SQLTransformer.this.start(this.outUri, this.outPrefix, name, attr);
+ }
+
+ private void end(String name) throws SAXException {
+ SQLTransformer.this.end(this.outUri, this.outPrefix, name);
+ }
+
+ private void data(String data) throws SAXException {
+ SQLTransformer.this.data(data);
+ }
+ }
+
+ private static class AncestorValue {
+ protected int level;
+ protected String name;
+
+ protected AncestorValue(int level, String name) {
+ this.level = level;
+ this.name = name;
+ }
+
+ public String toString() {
+ return "<ancestor level " + level + ", name " + name + ">";
+ }
+ }
+}
Propchange: cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cocoon/cocoon3/trunk/cocoon-databases/src/main/java/org/apache/cocoon/sax/component/SQLTransformer.java
------------------------------------------------------------------------------
svn:executable = *
Added: cocoon/cocoon3/trunk/cocoon-databases/src/main/resources/META-INF/cocoon/spring/cocoon-database-context.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-databases/src/main/resources/META-INF/cocoon/spring/cocoon-database-context.xml?rev=1171083&view=auto
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-databases/src/main/resources/META-INF/cocoon/spring/cocoon-database-context.xml (added)
+++ cocoon/cocoon3/trunk/cocoon-databases/src/main/resources/META-INF/cocoon/spring/cocoon-database-context.xml Thu Sep 15 12:53:41 2011
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for additional
+information regarding copyright ownership. The ASF licenses this file to
+You under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of
+the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+by applicable law or agreed to in writing, software distributed under the
+License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, either express or implied. See the License for the specific
+language governing permissions and limitations under the License. -->
+<!-- $Id: block-application-context.xml 697547 2008-09-21 16:52:48Z reinhard
+$ -->
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+ <bean name="transformer:sql" class="org.apache.cocoon.sax.component.SQLTransformer" scope="prototype" />
+</beans>
Propchange: cocoon/cocoon3/trunk/cocoon-databases/src/main/resources/META-INF/cocoon/spring/cocoon-database-context.xml
------------------------------------------------------------------------------
svn:eol-style = native
Modified: cocoon/cocoon3/trunk/parent/pom.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/parent/pom.xml?rev=1171083&r1=1171082&r2=1171083&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/parent/pom.xml (original)
+++ cocoon/cocoon3/trunk/parent/pom.xml Thu Sep 15 12:53:41 2011
@@ -432,6 +432,12 @@
<artifactId>xml-apis</artifactId>
<version>2.0.2</version>
</dependency>
+ <!-- Database support -->
+ <dependency>
+ <groupId>commons-dbcp</groupId>
+ <artifactId>commons-dbcp</artifactId>
+ <version>1.4</version>
+ </dependency>
<!-- Logging -->
<dependency>
@@ -473,6 +479,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.cocoon.databases</groupId>
+ <artifactId>cocoon-databases</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.cocoon.monitoring</groupId>
<artifactId>cocoon-monitoring</artifactId>
<version>${project.version}</version>
Modified: cocoon/cocoon3/trunk/pom.xml
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/pom.xml?rev=1171083&r1=1171082&r2=1171083&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/pom.xml (original)
+++ cocoon/cocoon3/trunk/pom.xml Thu Sep 15 12:53:41 2011
@@ -45,6 +45,8 @@
<module>cocoon-all</module>
<module>cocoon-controller</module>
<module>cocoon-monitoring</module>
+ <module>cocoon-databases</module>
+ <module>cocoon-databases-sample</module>
<module>cocoon-optional</module>
<module>cocoon-pipeline</module>
<module>cocoon-profiling</module>
@@ -129,4 +131,4 @@
</build>
</profile>
</profiles>
-</project>
\ No newline at end of file
+</project>