Advertisement
pavadeli

DatabaseProcedure.java

Jul 13th, 2011
1,042
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 5 27.81 KB | None | 0 0
  1. /*******************************************************************************
  2. Created by Paco van der Linden in his spare time, used in Oracle JHeadstart and
  3. several other products.
  4.  
  5. Notes       :
  6.  
  7. Open Issues :
  8.  
  9. $revision_history$
  10.  04-aug-2011   Paco van der Linden
  11.    1.4         oracleReturnValue is now correctly implemented
  12.  02-dec-2010   Paco van der LInden
  13.    1.3         Added support for CLOB types.
  14.  22-nov-2010   Paco van der Linden
  15.    1.2         Added custom parameter types and Oracle return types
  16.  04-oct-2010   Steven Davelaar
  17.    1.1         Added caching of procedures created in define method
  18.  03-jun-2009   Paco van der Linden
  19.    1.0         initial creation
  20.  
  21. ******************************************************************************/
  22. package oracle.jheadstart.util;
  23.  
  24. import java.sql.Clob;
  25. import java.sql.Connection;
  26. import java.sql.SQLException;
  27. import java.sql.Types;
  28.  
  29. import java.util.ArrayList;
  30. import java.util.HashMap;
  31. import java.util.Locale;
  32. import java.util.Map;
  33. import java.util.regex.Matcher;
  34. import java.util.regex.Pattern;
  35. import static java.util.regex.Pattern.CASE_INSENSITIVE;
  36. import static java.util.regex.Pattern.COMMENTS;
  37. import static java.util.regex.Pattern.DOTALL;
  38.  
  39. import oracle.jbo.CSMessageBundle;
  40. import oracle.jbo.SQLStmtException;
  41. import oracle.jbo.domain.Array;
  42. import oracle.jbo.domain.ClobDomain;
  43. import oracle.jbo.domain.Date;
  44. import oracle.jbo.domain.Number;
  45. import oracle.jbo.server.DBTransaction;
  46. import static oracle.jbo.server.DBTransaction.DEFAULT;
  47.  
  48. import oracle.jdbc.OracleCallableStatement;
  49.  
  50. import oracle.sql.ArrayDescriptor;
  51. import oracle.sql.CLOB;
  52. import oracle.sql.Datum;
  53. import oracle.sql.ORAData;
  54. import oracle.sql.ORADataFactory;
  55.  
  56. import oracle.adf.share.logging.ADFLogger;
  57.  
  58. import weblogic.utils.wrapper.Wrapper;
  59.  
  60.  
  61. /**
  62.  * <code>DatabaseProcedure</code> can be used to call (PL/SQL) stored procedures through a
  63.  * JDBC connection in a very intuitive way.
  64.  *
  65.  * <h3>Examples</h3>
  66.  * The functionality of this class is best explained through some examples. In the following examples
  67.  * the <code>connection</code> variable always points to a <code>{@link java.sql.Connection}</code> object.
  68.  * <p>
  69.  * Instead of a <code>java.sql.Connection</code> it is also possible to use an
  70.  * <code>{@link oracle.jbo.server.DBTransaction}</code>. This is especially useful when
  71.  * this class is used with ADF Business Components.
  72.  * <p>
  73.  *
  74.  * <b>Simple function call example</b><br>
  75.  * To call a database function (<code>hello_world</code>) and get the result of the call in
  76.  * a String variable, one only needs to use the following lines of
  77.  * code:
  78.  *
  79.  * <pre>
  80.  * String name = ...;
  81.  *
  82.  * DatabaseProcedure helloWorldProc =
  83.  * &nbsp;&nbsp;DatabaseProcedure.define("<i>function hello_world(p_name in varchar2) return varchar2</i>");
  84.  *
  85.  * String result = (String) helloWorldProc.call(connection, name).getReturnValue();
  86.  * </pre>
  87.  *
  88.  * <b>Simple procedure call example</b><br>
  89.  * To call a database procedure with several output parameters, do the following.
  90.  *
  91.  * <pre>
  92.  * Integer id = ...;
  93.  *
  94.  * DatabaseProcedure getEmpDetailsProc =
  95.  * &nbsp;&nbsp;DatabaseProcedure.define("<i>procedure get_employee_details
  96.  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( p_id in number
  97.  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;, p_name out varchar2
  98.  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;, p_address out varchar2
  99.  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;, p_age out number)</i>");
  100.  *
  101.  * DatabaseProcedure.Result result = getEmpDetailsProc.call(connection, id);
  102.  *
  103.  * String name = (String) result.getOutputValue("<i>p_name</i>");
  104.  * String address = (String) result.getOutputValue("<i>p_address</i>");
  105.  * Number age = (Number) result.getOutputValue("<i>p_age</i>");
  106.  * </pre>
  107.  *
  108.  * <b>Calling a function/procedure inside a package</b><br>
  109.  * To call a function or a procedure that resides inside a package, just prefix the name
  110.  * of the function/procedure with the package name.
  111.  *
  112.  * <pre>
  113.  * DatabaseProcedure helloWorldProc =
  114.  * &nbsp;&nbsp;DatabaseProcedure.define("<i>function my_package.hello_world(p_name in varchar2) return varchar2</i>");
  115.  * </pre>
  116.  *
  117.  * <h3>Recommended use</h3>
  118.  * A <code>DatabaseProcedure</code> object can be reused multiple times, comparable to {@link Pattern}.
  119.  * It is therefore best practice to store the <code>DatabaseProcedure</code> object in a static variable
  120.  * (as a constant).
  121.  * <p>
  122.  *
  123.  * <b>Proxies</b><br>
  124.  * A nice pattern is to wrap the call to a database procedure inside a stronly typed Java method. The
  125.  * hello world example shown earlier now looks like this:
  126.  * <pre>
  127.  * public class HelloWorldProxy
  128.  * {
  129.  * &nbsp;&nbsp;private static final DatabaseProcedure HELLO_WORLD =
  130.  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DatabaseProcedure.define("function hello_world(p_name in varchar2) return varchar2");
  131.  *
  132.  * &nbsp;&nbsp;public static String helloWorld(DBTransaction transaction, String name)
  133.  * &nbsp;&nbsp;{
  134.  * &nbsp;&nbsp;&nbsp;&nbsp;return (String) HELLO_WORLD.call(transaction, name).getReturnValue();
  135.  * &nbsp;&nbsp;}
  136.  * }
  137.  * </pre>
  138.  *
  139.  * <h3>Supported types</h3>
  140.  * By default the following types are supported:
  141.  * <ul>
  142.  * <li><code>varchar2</code>
  143.  * <li><code>number</code>
  144.  * <li><code>date</code>
  145.  * <li><code>integer</code>
  146.  * <li><code>clob</code>
  147.  * </ul>
  148.  *
  149.  * It is possible to register other types (both simple types and arrays and structs, etc.). See:
  150.  * <code>{@link #registerArrayType}</code>, <code>{@link #registerCustomParamType}</code> and
  151.  * <code>{@link #deregisterCustomParamType}</code>,
  152.  *
  153.  * @author Paco van der Linden
  154.  * @version 1.2
  155.  */
  156. public class DatabaseProcedure
  157. {
  158.   private static final ADFLogger sLog = ADFLogger.createADFLogger(DatabaseProcedure.class);
  159.  
  160.  
  161.   private static final Pattern PROCEDURE_DEFINITION =
  162.     Pattern.compile("\\s* (FUNCTION|PROCEDURE) \\s+ ([\\w.$]+) \\s* (?:\\((.*?)\\))? \\s* (?:RETURN\\s+(\\w+))? \\s* ;? \\s*",
  163.                     CASE_INSENSITIVE | COMMENTS | DOTALL);
  164.  
  165.   private static final Pattern PARAM_DEFINITION =
  166.     Pattern.compile("\\s* (\\w+) \\s+ (?:(IN)\\s+)? (?:(OUT)\\s+)? (\\w+) \\s* ,? \\s*", CASE_INSENSITIVE | COMMENTS);
  167.  
  168.   private static final Map<String, ParamType> PARAM_TYPES = new HashMap<String, ParamType>();
  169.   static
  170.   {
  171.     registerCustomParamType("VARCHAR2", Types.VARCHAR, null, null);
  172.     registerCustomParamType("NUMBER", Types.NUMERIC, Number.getORADataFactory(), null);
  173.     registerCustomParamType("DATE", Types.DATE, Date.getORADataFactory(), null);
  174.     registerCustomParamType("INTEGER", Types.INTEGER, Number.getORADataFactory(), null);
  175.     registerCustomParamType("CLOB", Types.CLOB, new ClobDomainFactory(), null);
  176.   }
  177.  
  178.   private static final Map<String, DatabaseProcedure> DEFINITION_CACHE = new HashMap<String, DatabaseProcedure>();
  179.  
  180.  
  181.   private final String escapeSequence;
  182.   private final ParamType returnType;
  183.   private final ParamDef[] paramDefs;
  184.   private final int maxParams;
  185.   private final int minParams;
  186.  
  187.  
  188.   private DatabaseProcedure(String name, ParamType returnType, ParamDef... params)
  189.   {
  190.     this.returnType = returnType;
  191.     this.paramDefs = params;
  192.     this.escapeSequence = createEscapeSequence(name, returnType != null, params.length);
  193.  
  194.     if (params == null || params.length == 0)
  195.     {
  196.       minParams = 0;
  197.       maxParams = 0;
  198.     }
  199.     else
  200.     {
  201.       int i = params.length;
  202.       maxParams = i;
  203.       while (i > 0 && !params[i - 1].in)
  204.       {
  205.         i--;
  206.       }
  207.       minParams = i;
  208.     }
  209.   }
  210.  
  211.   private static String createEscapeSequence(String name, boolean function, int noParams)
  212.   {
  213.     StringBuilder sb = new StringBuilder(32);
  214.     if (function)
  215.     {
  216.       sb.append("{?= call ");
  217.     }
  218.     else
  219.     {
  220.       sb.append("{call ");
  221.     }
  222.     sb.append(name);
  223.  
  224.     if (noParams > 0)
  225.     {
  226.       sb.append("(?");
  227.       for (int i = 1; i < noParams; i++)
  228.       {
  229.         sb.append(",?");
  230.       }
  231.       sb.append(")}");
  232.     }
  233.     else
  234.     {
  235.       sb.append("}");
  236.     }
  237.  
  238.     return sb.toString();
  239.   }
  240.  
  241.   /**
  242.    * The number of parameters of this defined procedure.
  243.    *
  244.    * @return the number of parameters
  245.    */
  246.   public int getParamCount()
  247.   {
  248.     return paramDefs.length;
  249.   }
  250.  
  251.   /**
  252.    * Returns the parameter definition at the specified index (zero based).
  253.    *
  254.    * @param paramIndex the parameter index
  255.    * @return the parameter definition
  256.    */
  257.   public ParamDef getParamDef(int paramIndex)
  258.   {
  259.     return paramDefs[paramIndex];
  260.   }
  261.  
  262.   /**
  263.    * Returns the parameter definition with the specified name.
  264.    *
  265.    * @param paramName the parameter name
  266.    * @return the parameter definition
  267.    * @throws IllegalArgumentException when paramName is unknown
  268.    */
  269.   public ParamDef getParamDef(String paramName)
  270.     throws IllegalArgumentException
  271.   {
  272.     for (int i = 0; i < paramDefs.length; i++)
  273.     {
  274.       if (paramDefs[i].name.equalsIgnoreCase(paramName))
  275.       {
  276.         return paramDefs[i];
  277.       }
  278.     }
  279.     throw new IllegalArgumentException("Unknown parameter name");
  280.   }
  281.  
  282.   /**
  283.    * The JDBC escape sequence corresponding to this defined procedure.
  284.    *
  285.    * @return the JDBC escape sequence
  286.    */
  287.   public String getEscapeSequence()
  288.   {
  289.     return escapeSequence;
  290.   }
  291.  
  292.   /**
  293.    * Call this DatabaseProcedure through the provided {@link DBTransaction}. The parameters are
  294.    * position based and need to include at least all IN-parameters.
  295.    *
  296.    * @param transaction the transaction to use
  297.    * @param parameters the parameters to the database procedure
  298.    * @return a {@link Result} object
  299.    */
  300.   public Result call(DBTransaction transaction, Object... parameters)
  301.   {
  302.     checkParameters(parameters);
  303.  
  304.     OracleCallableStatement call = (OracleCallableStatement) transaction.createCallableStatement(getEscapeSequence(), DEFAULT);
  305.     try
  306.     {
  307.       return _call(call, parameters);
  308.     }
  309.     catch (SQLException e)
  310.     {
  311.       sLog.severe("Failed to execute statement", e);
  312.       throw new SQLStmtException(CSMessageBundle.class, CSMessageBundle.EXC_SQL_EXECUTE_COMMAND, getEscapeSequence(), e);
  313.     }
  314.     finally
  315.     {
  316.       try
  317.       {
  318.         call.close();
  319.       }
  320.       catch (Exception e)
  321.       {
  322.         sLog.severe("Failed to close statement", e);
  323.       }
  324.     }
  325.   }
  326.  
  327.   /**
  328.    * Call this DatabaseProcedure through the provided {@link Connection}. The parameters are
  329.    * position based and need to include at least all IN-parameters.
  330.    *
  331.    * @param connection the connection to use
  332.    * @param parameters the parameters to the database procedure
  333.    * @return a {@link Result} object
  334.    * @throws SQLException
  335.    */
  336.   public Result call(Connection connection, Object... parameters)
  337.     throws SQLException
  338.   {
  339.     checkParameters(parameters);
  340.  
  341.     OracleCallableStatement call = (OracleCallableStatement) connection.prepareCall(getEscapeSequence());
  342.     try
  343.     {
  344.       return _call(call, parameters);
  345.     }
  346.     finally
  347.     {
  348.       try
  349.       {
  350.         call.close();
  351.       }
  352.       catch (Exception e)
  353.       {
  354.         sLog.severe("Failed to close statement", e);
  355.       }
  356.     }
  357.   }
  358.  
  359.   private void checkParameters(Object[] parameters)
  360.   {
  361.     if (parameters.length > maxParams)
  362.     {
  363.       throw new IllegalArgumentException("Too many parameters, maximum: " + maxParams);
  364.     }
  365.     else if (parameters.length < minParams)
  366.     {
  367.       throw new IllegalArgumentException("Not enough parameters, specify at least all IN parameters, minimum: " + minParams);
  368.     }
  369.   }
  370.  
  371.   private void _setInParam(OracleCallableStatement call, int index, ParamType type, Object value)
  372.     throws SQLException
  373.   {
  374.     String typeName = type.getTypeName();
  375.     int sqlTypeCode = type.getSqlTypeCode();
  376.  
  377.     if (sqlTypeCode == Types.CLOB && value instanceof String)
  378.     {
  379.       Connection connection = call.getConnection();
  380.       CLOB clob = CLOB.createTemporary(connection, true, CLOB.DURATION_SESSION);
  381.       clob.setString(1, (String) value);
  382.       call.setObject(index, clob);
  383.     }
  384.     else if (sqlTypeCode == Types.CLOB && value instanceof ClobDomain)
  385.     {
  386.       call.setObject(index, ((ClobDomain) value).toDatum(call.getConnection()));
  387.     }
  388.     else if (typeName == null)
  389.     {
  390.       call.setObject(index, value, sqlTypeCode);
  391.     }
  392.     else if (value == null)
  393.     {
  394.       call.setNull(index, sqlTypeCode, typeName);
  395.     }
  396.     else if (sqlTypeCode == Types.ARRAY && value instanceof Object[])
  397.     {
  398.       Connection connection = call.getConnection();
  399.       ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor(typeName, connection);
  400.       Array array = new Array(descriptor, connection, value);
  401.       call.setObject(index, array);
  402.     }
  403.     else
  404.     {
  405.       call.setObject(index, value);
  406.     }
  407.   }
  408.  
  409.   private void _setOutParam(OracleCallableStatement call, int index, ParamType type)
  410.     throws SQLException
  411.   {
  412.     String typeName = type.getTypeName();
  413.     int sqlTypeCode = type.getSqlTypeCode();
  414.  
  415.     if (typeName == null)
  416.     {
  417.       call.registerOutParameter(index, sqlTypeCode);
  418.     }
  419.     else
  420.     {
  421.       call.registerOutParameter(index, sqlTypeCode, typeName);
  422.     }
  423.   }
  424.  
  425.   private Result _call(OracleCallableStatement call, Object[] parameters)
  426.     throws SQLException
  427.   {
  428.     int offset = returnType != null? 2: 1;
  429.     for (int i = 0; i < paramDefs.length; i++)
  430.     {
  431.       ParamDef paramDef = paramDefs[i];
  432.       if (paramDef.in)
  433.       {
  434.         _setInParam(call, i + offset, paramDef.getType(), parameters[i]);
  435.       }
  436.       if (paramDef.out)
  437.       {
  438.         _setOutParam(call, i + offset, paramDef.getType());
  439.       }
  440.     }
  441.  
  442.     if (returnType != null)
  443.     {
  444.       _setOutParam(call, 1, returnType);
  445.     }
  446.  
  447.     call.execute();
  448.  
  449.     Result r = new Result(paramDefs);
  450.  
  451.     // Now retrieve both the standard JDBC objects and the Oracle objects (if available).
  452.     for (int i = 0; i < paramDefs.length; i++)
  453.     {
  454.       if (paramDefs[i].out)
  455.       {
  456.         r.paramValues[i] = getObject(call, i + offset);
  457.         r.paramOracleValues[i] = getOracleObject(call, i + offset, paramDefs[i].type);
  458.       }
  459.       else
  460.       {
  461.         r.paramValues[i] = null;
  462.         r.paramOracleValues[i] = null;
  463.       }
  464.     }
  465.  
  466.     if (returnType != null)
  467.     {
  468.       r.returnValue = getObject(call, 1);
  469.       r.oracleReturnValue = getOracleObject(call, 1, returnType);
  470.     }
  471.     else
  472.     {
  473.       r.returnValue = null;
  474.       r.oracleReturnValue = null;
  475.     }
  476.  
  477.     return r;
  478.   }
  479.  
  480.   private Object getObject(OracleCallableStatement call, int index)
  481.     throws SQLException
  482.   {
  483.     Object value = call.getObject(index);
  484.     if (value instanceof Wrapper)
  485.     {
  486.       return ((Datum) ((Wrapper) value).getVendorObj()).toJdbc();
  487.     }
  488.     else
  489.     {
  490.       return value;
  491.     }
  492.   }
  493.  
  494.   private ORAData getOracleObject(OracleCallableStatement call, int index, ParamType type)
  495.     throws SQLException
  496.   {
  497.     // We will use the provided ORADataFactory to create the Oracle object. Because of
  498.     // this we can also use ADFBC Domains, because these provide an ORADataFactory.
  499.     ORADataFactory factory = type.getORADataFactory();
  500.     if (factory == null)
  501.     {
  502.       return null;
  503.     }
  504.  
  505.     // The method getOracleObject does not work on WebLogic wrapped statements. Therefore we need to check
  506.     // if getObject returns a Wrapper class.
  507.     Datum oracleValue;
  508.     Object value = call.getObject(index);
  509.     if (value instanceof Wrapper)
  510.     {
  511.       oracleValue = (Datum) ((Wrapper) value).getVendorObj();
  512.     }
  513.     else
  514.     {
  515.       oracleValue = call.getOracleObject(index);
  516.     }
  517.  
  518.     if (oracleValue != null)
  519.     {
  520.       return factory.create(oracleValue, type.getSqlTypeCode());
  521.     }
  522.     else
  523.     {
  524.       return null;
  525.     }
  526.   }
  527.  
  528.   /**
  529.    * <b>Advanced:</b> <em>Not yet fully tested / documented.</em>
  530.    * <p>
  531.    * Provide a custom parameter type that can subsequently be used
  532.    * in any DatabaseProcedure definition.
  533.    *
  534.    * @param name the name of the parameter as will be used inside the procedure definition
  535.    * @param sqlTypeCode the {@link Types JDBC SQL Type}
  536.    * @param dataFactory the ORADataFactory if available (for use with ADF BC Domains)
  537.    * @param typeName the case-sensitive name with which this type is known in the database.
  538.    *
  539.    * @see Types
  540.    * @see #registerArrayType
  541.    */
  542.   public static void registerCustomParamType(String name, int sqlTypeCode, ORADataFactory dataFactory, String typeName)
  543.   {
  544.     String uName = name.toUpperCase(Locale.ENGLISH);
  545.     PARAM_TYPES.put(uName, new ParamType(uName, sqlTypeCode, dataFactory, typeName));
  546.   }
  547.  
  548.   /**
  549.    * <b>Advanced:</b> <em>Not yet fully tested / documented.</em>
  550.    * <p>
  551.    * Register an array type. The type can subsequently be used in any DatabaseProcedure
  552.    * definition.
  553.    *
  554.    * @param name the case-sensitive name of this array type in the database.
  555.    */
  556.   public static void registerArrayType(String name)
  557.   {
  558.     registerCustomParamType(name, Types.ARRAY, Array.getORADataFactory(), name);
  559.   }
  560.  
  561.   /**
  562.    * <b>Advanced:</b> <em>Not yet fully tested / documented.</em>
  563.    * <p>
  564.    * Unregister a custom type.
  565.    *
  566.    * @param name the name of the type
  567.    * @return <code>true</code> iff a type was unregistered
  568.    */
  569.   public static boolean deregisterCustomParamType(String name)
  570.   {
  571.     return PARAM_TYPES.remove(name.toUpperCase(Locale.ENGLISH)) != null;
  572.   }
  573.  
  574.   /**
  575.    * Construct a DatabaseProcedure from its (original) PL/SQL declaration.
  576.    *
  577.    * <p>
  578.    *
  579.    * Function declaration:
  580.    * <blockquote><pre>function &lt;name&gt; [(&lt;param&gt;[, &lt;param&gt;,...])] return &lt;return-type&gt;</pre></blockquote>
  581.    *
  582.    * Procedure declaration:
  583.    * <blockquote><pre>procedure &lt;name&gt; [(&lt;param&gt;[, &lt;param&gt;,...])]</pre></blockquote>
  584.    *
  585.    * With the optional <code>&lt;param&gt;</code> as:
  586.    * <blockquote><pre>&lt;param-name&gt; [in] [out] &lt;param-type&gt;</pre></blockquote>
  587.    *
  588.    * <p>
  589.    *
  590.    * Example of a function with one parameter returning a <code>varchar2</code>:
  591.    * <blockquote><pre>function hr.hello_world(p_name in out varchar2) return varchar2</pre></blockquote>
  592.    *
  593.    * Example of a procedure with one input and one output parameter:
  594.    * <blockquote><pre>procedure hr.hello_world( p_name in varchar2, p_msg out varchar2)</pre></blockquote>
  595.    *
  596.    * <p>
  597.    *
  598.    * See {@link DatabaseProcedure class documentation} for the possible parametertypes.
  599.    *
  600.    * @param declaration the procedure declaration in PL/SQL style
  601.    * @return a DatabaseProcedure object
  602.    */
  603.   public static DatabaseProcedure define(String declaration)
  604.   {
  605.     if (DEFINITION_CACHE.containsKey(declaration))
  606.     {
  607.       return DEFINITION_CACHE.get(declaration);
  608.     }
  609.     DatabaseProcedure proc;
  610.  
  611.     Matcher m = PROCEDURE_DEFINITION.matcher(declaration);
  612.     if (!m.matches())
  613.     {
  614.       compileError("Declaration is not valid", declaration);
  615.     }
  616.  
  617.     boolean function = m.group(1).equalsIgnoreCase("FUNCTION");
  618.     String name = m.group(2);
  619.     String params = m.group(3);
  620.     String returns = m.group(4);
  621.  
  622.     ParamType returnType = null;
  623.     if (function)
  624.     {
  625.       if (returns == null)
  626.       {
  627.         compileError("A function needs a return type", declaration);
  628.       }
  629.       returnType = PARAM_TYPES.get(returns.toUpperCase(Locale.ENGLISH));
  630.       if (returnType == null)
  631.       {
  632.         compileError("Unsupported type (" + returns + ")", declaration);
  633.       }
  634.     }
  635.     else if (returns != null)
  636.     {
  637.       compileError("A procedure has no return type", declaration);
  638.     }
  639.  
  640.     if (params != null)
  641.     {
  642.       ArrayList<ParamDef> paramList = new ArrayList<ParamDef>();
  643.       m = PARAM_DEFINITION.matcher(params);
  644.       while (!m.hitEnd())
  645.       {
  646.         if (!m.lookingAt())
  647.         {
  648.           compileError("Parameter declaration not valid", params.substring(m.regionStart()));
  649.         }
  650.  
  651.         try
  652.         {
  653.           paramList.add(new ParamDef(m.group(1), m.group(2) != null || m.group(3) == null, m.group(3) != null, m.group(4)));
  654.         }
  655.         catch (IllegalArgumentException e)
  656.         {
  657.           compileError(e.getMessage(), declaration);
  658.         }
  659.  
  660.         // Remove parameter from search window
  661.         m.region(m.end(), m.regionEnd());
  662.       }
  663.  
  664.       proc = new DatabaseProcedure(name, returnType, paramList.toArray(new DatabaseProcedure.ParamDef[paramList.size()]));
  665.     }
  666.     else
  667.     {
  668.       proc = new DatabaseProcedure(name, returnType);
  669.     }
  670.  
  671.     DEFINITION_CACHE.put(declaration, proc);
  672.     return proc;
  673.   }
  674.  
  675.   private static void compileError(String msg, String declaration)
  676.   {
  677.     throw new IllegalArgumentException(msg + ": " + declaration);
  678.   }
  679.  
  680.   /**
  681.    * The definition of a parameter.
  682.    */
  683.   public static final class ParamDef
  684.   {
  685.     private final String name;
  686.     private final boolean in;
  687.     private final boolean out;
  688.     private final ParamType type;
  689.  
  690.     private ParamDef(String name, boolean in, boolean out, String type)
  691.     {
  692.       if (!in && !out)
  693.       {
  694.         throw new IllegalArgumentException("A parameter should be IN or OUT or both");
  695.       }
  696.  
  697.       this.name = name;
  698.       this.in = in;
  699.       this.out = out;
  700.       this.type = PARAM_TYPES.get(type.toUpperCase(Locale.ENGLISH));
  701.       if (this.type == null)
  702.       {
  703.         throw new IllegalArgumentException("Unsupported type (" + type + ")");
  704.       }
  705.     }
  706.  
  707.     /**
  708.      * The name of the parameter.
  709.      *
  710.      * @return name
  711.      */
  712.     public String getName()
  713.     {
  714.       return name;
  715.     }
  716.  
  717.     /**
  718.      * Indicates whether the parameter is used as in input parameter.
  719.      *
  720.      * @return true if the parameter is an input parameter
  721.      */
  722.     public boolean isIn()
  723.     {
  724.       return in;
  725.     }
  726.  
  727.     /**
  728.      * Indicates whether the parameter is used as in output parameter.
  729.      *
  730.      * @return true if the parameter is an output parameter
  731.      */
  732.     public boolean isOut()
  733.     {
  734.       return out;
  735.     }
  736.  
  737.     /**
  738.      * The type of the parameter.
  739.      *
  740.      * @return type
  741.      */
  742.     public ParamType getType()
  743.     {
  744.       return type;
  745.     }
  746.   }
  747.  
  748.   /**
  749.    * The result of a call to a DatabaseProcedure.
  750.    */
  751.   public static class Result
  752.   {
  753.     protected final Object[] paramValues;
  754.     protected final ORAData[] paramOracleValues;
  755.     protected final ParamDef[] paramDefs;
  756.     protected Object returnValue;
  757.     protected ORAData oracleReturnValue;
  758.  
  759.     protected Result(ParamDef[] paramDefs)
  760.     {
  761.       this.paramDefs = paramDefs;
  762.       this.paramValues = new Object[paramDefs.length];
  763.       this.paramOracleValues = new ORAData[paramDefs.length];
  764.     }
  765.  
  766.     protected Result(Result otherResult)
  767.     {
  768.       this.paramDefs = otherResult.paramDefs;
  769.       this.paramValues = otherResult.paramValues.clone();
  770.       this.paramOracleValues = otherResult.paramOracleValues.clone();
  771.       this.returnValue = otherResult.returnValue;
  772.       this.oracleReturnValue = otherResult.oracleReturnValue;
  773.     }
  774.  
  775.     /**
  776.      * The returned value of an out parameter. Parameters that have not been marked as out
  777.      * parameters always return <code>null</code>.
  778.      *
  779.      * @param paramIndex a valid parameter index (zero-based)
  780.      * @return outputvalue of the parameter
  781.      */
  782.     public Object getOutputValue(int paramIndex)
  783.     {
  784.       return paramValues[paramIndex];
  785.     }
  786.  
  787.     /**
  788.      * The returned value of an out parameter as an Oracle object. Parameters that have
  789.      * not been marked as out parameters always return <code>null</code>.
  790.      *
  791.      * @param paramIndex a valid parameter index (zero-based)
  792.      * @return outputvalue of the parameter as an Oracle object.
  793.      */
  794.     public Object getOracleOutputValue(int paramIndex)
  795.     {
  796.       Object value = paramOracleValues[paramIndex];
  797.       if (value == null)
  798.       {
  799.         value = paramValues[paramIndex];
  800.       }
  801.       return value;
  802.     }
  803.  
  804.     protected int getParamIndex(String paramName)
  805.     {
  806.       for (int i = 0; i < paramDefs.length; i++)
  807.       {
  808.         if (paramDefs[i].name.equalsIgnoreCase(paramName))
  809.         {
  810.           return i;
  811.         }
  812.       }
  813.       throw new IllegalArgumentException("Unknown parameter name");
  814.     }
  815.  
  816.     /**
  817.      * The returned value of an out parameter. Parameters that have not been marked as out
  818.      * parameters always return <code>null</code>.
  819.      *
  820.      * @param paramName a valid parameter name
  821.      * @return outputvalue of the parameter
  822.      * @throws IllegalArgumentException if paramName is unknown
  823.      */
  824.     public Object getOutputValue(String paramName)
  825.       throws IllegalArgumentException
  826.     {
  827.       return getOutputValue(getParamIndex(paramName));
  828.     }
  829.  
  830.     /**
  831.      * The returned value of an out parameter as an Oracle object. Parameters that have
  832.      * not been marked as out parameters always return <code>null</code>.
  833.      *
  834.      * @param paramName a valid parameter name
  835.      * @return outputvalue of the parameter as an Oracle object.
  836.      * @throws IllegalArgumentException if paramName is unknown
  837.      */
  838.     public Object getOracleOutputValue(String paramName)
  839.     {
  840.       return getOracleOutputValue(getParamIndex(paramName));
  841.     }
  842.  
  843.     /**
  844.      * The returned value of a function. Returns null in case of procedures.
  845.      *
  846.      * @return returnvalue of a function or null in case of a procedure
  847.      */
  848.     public Object getReturnValue()
  849.     {
  850.       return returnValue;
  851.     }
  852.  
  853.     /**
  854.      * The returned value of a function as an Oracle type. Returns null in case of procedures.
  855.      *
  856.      * @return returnvalue of a function or null in case of a procedure
  857.      */
  858.     public Object getOracleReturnValue()
  859.     {
  860.       if (oracleReturnValue != null)
  861.       {
  862.         return oracleReturnValue;
  863.       }
  864.       else
  865.       {
  866.         return returnValue;
  867.       }
  868.     }
  869.   }
  870.  
  871.  
  872.   public static class ParamType
  873.   {
  874.     private final String name;
  875.     private final int sqlTypeCode;
  876.     private final ORADataFactory dataFactory;
  877.     private final String typeName;
  878.  
  879.     private ParamType(String name, int sqlTypeCode, ORADataFactory dataFactory, String typeName)
  880.     {
  881.       this.name = name;
  882.       this.sqlTypeCode = sqlTypeCode;
  883.       this.dataFactory = dataFactory;
  884.       this.typeName = typeName;
  885.     }
  886.  
  887.     public int getSqlTypeCode()
  888.     {
  889.       return sqlTypeCode;
  890.     }
  891.  
  892.     public ORADataFactory getORADataFactory()
  893.     {
  894.       return dataFactory;
  895.     }
  896.  
  897.     public String getName()
  898.     {
  899.       return name;
  900.     }
  901.  
  902.     public String getTypeName()
  903.     {
  904.       return typeName;
  905.     }
  906.  
  907.     @Override
  908.     public String toString()
  909.     {
  910.       return getName();
  911.     }
  912.   }
  913.  
  914.   private static final class ClobDomainFactory
  915.     implements ORADataFactory
  916.   {
  917.     public ORAData create(Datum datum, int i)
  918.     {
  919.       return datum != null? new ClobDomain((Clob) datum): null;
  920.     }
  921.   }
  922.  
  923.   public static void main(String... args)
  924.   {
  925.     DatabaseProcedure.define("function hr.hello_world(p_name in out varchar2) return varchar2").dumpInfo();
  926.     DatabaseProcedure.define("procedure hr.hello_world( p_name varchar2" +
  927.                              "                        , p_msg out varchar2)").dumpInfo();
  928.  
  929.     DatabaseProcedure.registerCustomParamType("custom_type", Types.ARRAY, Array.getORADataFactory(), "custom_type_tab");
  930.     DatabaseProcedure.define("function test(p_in in varchar2) return custom_type").dumpInfo();
  931.   }
  932.  
  933.   private void dumpInfo()
  934.   {
  935.     System.out.println("JDBC escape sequence: " + getEscapeSequence());
  936.     System.out.println("Type: " + (returnType == null? "PROCEDURE": "FUNCTION"));
  937.     if (returnType != null)
  938.     {
  939.       System.out.println("Returntype: " + returnType);
  940.     }
  941.     System.out.println();
  942.     for (ParamDef pd: paramDefs)
  943.     {
  944.       System.out.println("Param: " + pd.name.toUpperCase());
  945.       System.out.println("Type: " + pd.type);
  946.       System.out.println("In/out: " + (pd.in? "IN ": "") + (pd.out? "OUT": ""));
  947.       System.out.println();
  948.     }
  949.   }
  950. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement