Package org.exist.xquery.modules.oracle

Source Code of org.exist.xquery.modules.oracle.ExecuteFunction

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2010 The eXist Project
*  http://exist-db.org
*
*  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
*  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.
*
*  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 St, Fifth Floor, Boston, MA  02110-1301  USA
*
*  $Id$
*/
package org.exist.xquery.modules.oracle;

import java.io.PrintStream;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLRecoverableException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import oracle.jdbc.OracleTypes;

import org.apache.log4j.Logger;
import org.exist.Namespaces;
import org.exist.dom.QName;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.exist.memtree.MemTreeBuilder;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.modules.sql.SQLModule;
import org.exist.xquery.modules.sql.SQLUtils;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.IntegerValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* eXist Oracle Module Extension ExecuteFunction
*
* Execute a PL/SQL stored procedure within an Oracle RDBMS.
*
* @author Robert Walpole <robert.walpole@metoffice.gov.uk>
* @serial 2009-03-23
* @version 1.0
*
* @see org.exist.xquery.BasicFunction#BasicFunction(org.exist.xquery.XQueryContext,
*      org.exist.xquery.FunctionSignature)
*/
public class ExecuteFunction extends BasicFunction {
   
    private static final Logger LOG = Logger.getLogger(ExecuteFunction.class);
 
  final static FunctionSignature[] signatures = {
    new FunctionSignature(
      new QName( "execute", OracleModule.NAMESPACE_URI, OracleModule.PREFIX ),
          "Executes a PL/SQL stored procedure passed as the second argument against an Oracle RDBMS specified by the connection " +
          "in the first argument with the position of the result set cursor at the fourth argument. Stored procedure parameters " +
          "may be passed in the third argument using an XML fragment with the following structure: " +
          "<oracle:parameters><orace:param oracle:pos=\"{param-position}\" oracle:type=\"{param-type}\"/>{param-value}" +
      "</oracle:parameters>.",
          new SequenceType[] {
        new FunctionParameterSequenceType( "connection-handle", Type.INTEGER, Cardinality.EXACTLY_ONE, "The connection handle" ),
              new FunctionParameterSequenceType( "plsql-statement", Type.STRING, Cardinality.EXACTLY_ONE, "The PL/SQL stored procedure"),
              new FunctionParameterSequenceType( "parameters", Type.ELEMENT, Cardinality.ZERO_OR_ONE, "Input parameters for the stored procedure (if any)" ),
              new FunctionParameterSequenceType( "result-set-position", Type.INTEGER, Cardinality.ZERO_OR_ONE, "The position of the result set cursor"),
              new FunctionParameterSequenceType( "make-node-from-column-name", Type.BOOLEAN, Cardinality.EXACTLY_ONE, "The flag that indicates whether " + "" +
                  "the xml nodes should be formed from the column names (in this mode a space in a Column Name will be replaced by an underscore!)" )
      },
      new FunctionReturnSequenceType( Type.NODE, Cardinality.ZERO_OR_ONE, "the results" )
    ),
    new FunctionSignature(
        new QName( "execute", OracleModule.NAMESPACE_URI, OracleModule.PREFIX ),
        "Executes a PL/SQL stored procedure passed as the second argument against an Oracle RDBMS specified by the connection " +
            "in the first argument with the position of the result set cursor at the fourth argument. Stored procedure parameters " +
            "may be passed in the third argument using an XML fragment with the following structure: " +
            "<oracle:parameters><orace:param oracle:pos=\"{param-position}\" oracle:type=\"{param-type}\"/>{param-value}" +
        "</oracle:parameters>. An additional return code parameter is supported which can be used to specify an integer value returned " +
        "in the first position of the statement to indicate success of the PL/SQL call.",
            new SequenceType[] {
          new FunctionParameterSequenceType( "connection-handle", Type.INTEGER, Cardinality.EXACTLY_ONE, "The connection handle" ),
                new FunctionParameterSequenceType( "plsql-statement", Type.STRING, Cardinality.EXACTLY_ONE, "The PL/SQL stored procedure" ),
                new FunctionParameterSequenceType( "parameters", Type.ELEMENT, Cardinality.ZERO_OR_ONE, "Input parameters for the stored procedure (if any)" ),
                new FunctionParameterSequenceType( "result-set-position", Type.INTEGER, Cardinality.EXACTLY_ONE, "The position of the result set cursor" ),
                new FunctionParameterSequenceType( "make-node-from-column-name", Type.BOOLEAN, Cardinality.EXACTLY_ONE, "The flag that indicates whether " +
                    "the xml nodes should be formed from the column names (in this mode a space in a Column Name will be replaced by an underscore!)" ),
                new FunctionParameterSequenceType( "return-code", Type.INTEGER, Cardinality.EXACTLY_ONE, "The expected function return code which indicates successful execution" )
        },
        new FunctionReturnSequenceType( Type.NODE, Cardinality.ZERO_OR_ONE, "the results" )
      )
  };
 
  private final static String PARAMETERS_ELEMENT_NAME = "parameters";
  private final static String PARAM_ELEMENT_NAME = "param";
    private final static String TYPE_ATTRIBUTE_NAME = "type";
    private final static String POSITION_ATTRIBUTE_NAME = "pos";
   
    private DateFormat xmlDf;

  /**
     * ExecuteFunction Constructor
     *
     * @param context
     *            The Context of the calling XQuery
     */
    public ExecuteFunction( XQueryContext context, FunctionSignature signature ) {
        super( context, signature );
        xmlDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    }
   
  @Override
  public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
   
    if(args.length == 5 || args.length == 6) {
      // was a connection and PL/SQL statement specified?
      if( args[0].isEmpty() || args[1].isEmpty() ) {
        return( Sequence.EMPTY_SEQUENCE );
      }
     
      // get the Connection
      long connectionUID = ((IntegerValue) args[0].itemAt(0)).getLong();
      Connection connection = SQLModule.retrieveConnection( context, connectionUID );
     
      if( connection == null ) {
        return( Sequence.EMPTY_SEQUENCE );
      }
     
      // get the PL/SQL statement
      String plSql = args[1].getStringValue();
     
      // get the input parameters (if any)
      Element parameters = null;
      if(!args[2].isEmpty()) {
        parameters = (Element)args[2].itemAt(0);
      }
     
      // was a result set position specified?
      int resultSetPos = 0;
      if(!args[3].isEmpty())   {
        resultSetPos = ((IntegerValue) args[3].itemAt(0)).getInt();
      }
     
      boolean haveReturnCode = false;
      int plSqlSuccess = 1// default value of 1 for success
      if(args.length == 6) {
        // a return code is expected so what is the value indicating success?
         plSqlSuccess = ((IntegerValue) args[5].itemAt(0)).getInt();
         haveReturnCode = true;
      }

      CallableStatement statement = null;
      ResultSet resultSet = null;
     
      try {
        MemTreeBuilder builder = context.getDocumentBuilder();
        int iRow = 0;
       
        statement = connection.prepareCall(plSql);
        if(haveReturnCode)
        {
          statement.registerOutParameter(1, Types.NUMERIC);
        }     
        if(resultSetPos != 0)
        {
          statement.registerOutParameter(resultSetPos, OracleTypes.CURSOR);
        }
        if(!args[2].isEmpty())
        {
          setParametersOnPreparedStatement(statement, parameters);
        }
             
        statement.execute();
       
        if(haveReturnCode) {
          int returnCode = statement.getInt(1);
          if(returnCode != plSqlSuccess) {
            LOG.error(plSql + " failed [" + returnCode + "]");
            return( Sequence.EMPTY_SEQUENCE );
          }
        }     
       
        if(resultSetPos != 0) {
          // iterate through the result set building an XML document
          builder.startDocument();
           
          builder.startElement(new QName("result", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
          builder.addAttribute(new QName("count", null, null), String.valueOf(-1 ));
           
          resultSet = (ResultSet)statement.getObject(resultSetPos);
           
          ResultSetMetaData rsmd = resultSet.getMetaData();
          int iColumns = rsmd.getColumnCount();
           
          while (resultSet.next())
          {
            builder.startElement(new QName("row", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
                builder.addAttribute(new QName("index", null, null), String.valueOf(resultSet.getRow()));

            // get each tuple in the row
            for(int i = 0; i < iColumns; i++)
            {
              String columnName = rsmd.getColumnLabel(i + 1);
              if(columnName != null)
              {
                String colValue = resultSet.getString(i + 1);
                 
                String colElement = "field";

                if(((BooleanValue)args[4].itemAt(0)).effectiveBooleanValue() && columnName.length() > 0)
                {
                  // use column names as the XML node

                  /**
                   * Spaces in column names are replaced with
                   * underscore's
                   */
                   
                  colElement = SQLUtils.escapeXmlAttr(columnName.replace(' ', '_'));
                }
               
                builder.startElement(new QName(colElement, OracleModule.NAMESPACE_URI, OracleModule.PREFIX ), null);
               
                if(!((BooleanValue)args[4].itemAt(0)).effectiveBooleanValue() || columnName.length() <= 0)
                {
                  String name;
                 
                  if(columnName.length() > 0) {
                    name = SQLUtils.escapeXmlAttr(columnName);
                  } else {
                    name = "Column: " + String.valueOf(i + 1);
                  }
                 
                  builder.addAttribute(new QName("name", null, null), name);
                }
               
                builder.addAttribute(new QName("type", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), rsmd.getColumnTypeName(i + 1));
                builder.addAttribute(new QName("type", Namespaces.SCHEMA_NS, "xs"), Type.getTypeName(SQLUtils.sqlTypeToXMLType(rsmd.getColumnType(i + 1))));
               
                if(resultSet.wasNull())
                {
                  // Add a null indicator attribute if the value was SQL Null
                  builder.addAttribute(new QName("null", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), "true");
                }
               
                if(colValue != null)
                {
                  builder.characters(SQLUtils.escapeXmlText(colValue));
                }
               
                builder.endElement();
              }
            }

            builder.endElement();
             
            iRow++;
          }
          builder.endElement();
         
          // Change the root element count attribute to have the correct value
         
          NodeValue node = (NodeValue)builder.getDocument().getDocumentElement();

          Node count = node.getNode().getAttributes().getNamedItem("count");
         
          if(count != null)
          {
            count.setNodeValue(String.valueOf(iRow));
          }
          builder.endDocument();
         
          // return the XML result set
          return(node);
        }
        else
        {
          // there was no result set so just return an empty sequence
          return( Sequence.EMPTY_SEQUENCE );
        }
      }
      catch(SQLException sqle) {
       
        LOG.error("oracle:execute() Caught SQLException \"" + sqle.getMessage() + "\" for PL/SQL: \"" + plSql + "\"", sqle);
       
        //return details about the SQLException
        MemTreeBuilder builder = context.getDocumentBuilder();
       
        builder.startDocument();
        builder.startElement(new QName("exception", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
       
        boolean recoverable = false;
        if(sqle instanceof SQLRecoverableException)
        {
          recoverable = true;
        }
        builder.addAttribute(new QName("recoverable", null, null), String.valueOf(recoverable));
             
        builder.startElement(new QName("state", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
        String sqlState = sqle.getSQLState();
        if(sqlState != null) {
          builder.characters(sqle.getSQLState());
        }
        else {
          builder.characters("null");
        }
                       
        builder.endElement();
       
        builder.startElement(new QName("message", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
        builder.characters(sqle.getMessage());
        builder.endElement();
       
        builder.startElement(new QName("stack-trace", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
        ByteArrayOutputStream bufStackTrace = new ByteArrayOutputStream();
        sqle.printStackTrace(new PrintStream(bufStackTrace));
        builder.characters(new String(bufStackTrace.toByteArray()));
        builder.endElement();
       
        builder.startElement(new QName("oracle", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
        builder.characters(SQLUtils.escapeXmlText(plSql));
        builder.endElement();
       
          int line = getLine();
          int column = getColumn();
         
          builder.startElement(new QName("xquery", OracleModule.NAMESPACE_URI, OracleModule.PREFIX), null);
          builder.addAttribute(new QName("line", null, null), String.valueOf(line));
          builder.addAttribute(new QName("column", null, null), String.valueOf(column));
          builder.endElement();

        builder.endElement();
        builder.endDocument();
       
        return (NodeValue)builder.getDocument().getDocumentElement();   
      }
      finally {
        release(connection, statement, resultSet);
      }
        }
        else {
          throw new XPathException("Invalid number of arguments [" + args.length + "]");
        }
  }
 
  /**
   * Release DB resources
   * @param connection
   * @param statement
   * @param rs
   */
  protected void release(Connection connection, Statement statement, ResultSet rs) {
    if (rs != null) {
      try {
        rs.close();
      }
      catch (SQLException sqle) {
        LOG.error("Unable to close ResultSet: ", sqle);
      }
    }
    if (statement != null) {
      try {
        statement.close();
      }
      catch (SQLException sqle) {
        LOG.error("Unable to close Statement: ", sqle);
      }
    }
    if (connection != null) {
      try {
        connection.close();
      }
      catch (SQLException sqle) {
        LOG.error("Unable to close Connection: ", sqle);
      }
    }
  }
 
  private void setParametersOnPreparedStatement(Statement stmt, Element parametersElement) throws SQLException, XPathException {
   
        if(parametersElement.getNamespaceURI().equals(OracleModule.NAMESPACE_URI) && parametersElement.getLocalName().equals(PARAMETERS_ELEMENT_NAME))
        {
            NodeList paramElements = parametersElement.getElementsByTagNameNS(OracleModule.NAMESPACE_URI, PARAM_ELEMENT_NAME);

            for(int i = 0; i < paramElements.getLength(); i++)
            {
                Element param = ((Element)paramElements.item(i));
                String value = param.getFirstChild().getNodeValue();
                String type = param.getAttributeNS(OracleModule.NAMESPACE_URI, TYPE_ATTRIBUTE_NAME);
                int position = Integer.parseInt(param.getAttributeNS(OracleModule.NAMESPACE_URI, POSITION_ATTRIBUTE_NAME));
                try {
                  int sqlType = SQLUtils.sqlTypeFromString(type);
                  // What if SQL type is date???
                  if(sqlType == Types.DATE) {
                    Date date = xmlDf.parse(value);
                    ((PreparedStatement)stmt).setTimestamp(position, new Timestamp(date.getTime()));
                  }
                  else {
                    ((PreparedStatement)stmt).setObject(position, value, sqlType);
                  }
                }
                catch (ParseException pex) {
                  throw new XPathException(this, "Unable to parse date from value " + value + ". Expected format is YYYY-MM-DDThh:mm:ss.sss");
                }
                catch (Exception ex) {
                  throw new XPathException(this, "Failed to set stored procedure parameter at position " + position + " as " + type + " with value " + value);
                }
            }
        }
    }

}
TOP

Related Classes of org.exist.xquery.modules.oracle.ExecuteFunction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.