You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beehive.apache.org by cs...@apache.org on 2006/03/14 21:21:48 UTC

svn commit: r385874 - in /beehive/trunk/system-controls: src/jdbc/org/apache/beehive/controls/system/jdbc/ src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ test/jdbc/junit...

Author: cschoett
Date: Tue Mar 14 12:21:45 2006
New Revision: 385874

URL: http://svn.apache.org/viewcvs?rev=385874&view=rev
Log:
Added the ablity for the JdbcControl to perform additional evaluation on ComplexSqlFragments when resolving sql statements.
This modification allows a jdbc control method parameter to return an instance of a ComplexSqlFragment which contains SQL text 
AND prepared statement parameters and have the jdbc control resolve it correctly.  For example, the SQL text 
could be something like: 'WHERE Name = ?' and the parameter value would be the value subsitiuted into the resulting 
prepared statement.

Also added new DRTs to test this feature.


Modified:
    beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControl.java
    beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ReflectionFragment.java
    beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlFragment.java
    beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlSubstitutionFragment.java
    beehive/trunk/system-controls/test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ResultsTestCtrl.jcx
    beehive/trunk/system-controls/test/jdbc/junitTests/org/apache/beehive/controls/system/jdbc/units/results/DBSingleRowResultsTest.java

Modified: beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControl.java
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControl.java?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControl.java (original)
+++ beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControl.java Tue Mar 14 12:21:45 2006
@@ -36,6 +36,8 @@
 import java.sql.Types;
 import java.sql.SQLData;
 import java.util.Calendar;
+import java.util.List;
+import java.util.Arrays;
 
 /**
  * Simplifies access to a relational database from your Java code using SQL commands.
@@ -561,6 +563,66 @@
          */
         public Object clone() {
             return new SQLParameter(value, type, dir);
+        }
+    }
+
+    /**
+     * A ComplexSqlFragment can be used as a return value from a parameter reflection operation for
+     * return values which contain BOTH SQL text and parameters.  For Example, the text portion
+     * could be something like 'where NAME = ?' and the parameter value is 'Fred'.
+     */
+    public static class ComplexSqlFragment {
+
+        protected CharSequence sql;
+        protected List<SQLParameter> parameters;
+
+        /**
+         * Create a new SQLFragment.
+         */
+        public ComplexSqlFragment() {
+            sql = null;
+            parameters = null;
+        }
+
+        /**
+         * Create a new SQLFragment with the specified SQL and parameter list.
+         *
+         * @param sql        SQL contents of the fragment.
+         * @param parameters Substitution parameters.
+         */
+        public ComplexSqlFragment(String sql, SQLParameter[] parameters) {
+            this.sql = sql;
+            if (null != parameters)
+                this.parameters = Arrays.asList(parameters);
+        }
+
+        /**
+         * Get the SQL of this fragment.
+         *
+         * @return String.
+         */
+        public String getSQL() {
+            return sql.toString();
+        }
+
+        /**
+         * Get the parameters contained within this fragment.
+         * Returns a zero-based array.
+         *
+         * @return SQLParameter array.
+         */
+        public SQLParameter[] getParameters() {
+            if (null == parameters)
+                return new SQLParameter[0];
+            return parameters.toArray(new SQLParameter[parameters.size()]);
+        }
+
+        /**
+         * Get the SQL string contained within this fragment.
+         * @return String.
+         */
+        public String toString() {
+            return sql.toString();
         }
     }
 }

Modified: beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ReflectionFragment.java
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ReflectionFragment.java?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ReflectionFragment.java (original)
+++ beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/ReflectionFragment.java Tue Mar 14 12:21:45 2006
@@ -21,6 +21,7 @@
 import org.apache.beehive.controls.api.ControlException;
 import org.apache.beehive.controls.api.context.ControlBeanContext;
 import org.apache.beehive.controls.system.jdbc.TypeMappingsFactory;
+import org.apache.beehive.controls.system.jdbc.JdbcControl;
 
 import java.lang.reflect.Method;
 import java.util.Map;
@@ -49,7 +50,7 @@
      *
      * @param parameterName The name of the parameter whose value should be substituted at this location.
      */
-    ReflectionFragment(String parameterName) {
+    protected ReflectionFragment(String parameterName) {
         _parameterName = parameterName;
         _sqlDataType = TypeMappingsFactory.TYPE_UNKNOWN;
         _nameQualifiers = s_parameterNamePattern.split(_parameterName);
@@ -61,7 +62,7 @@
      * @param parameterName The name of the parameter whose value should be substituted at this location.
      * @param sqlDataType   A String specifing the SQL data type for this parameter.
      */
-    ReflectionFragment(String parameterName, String sqlDataType) {
+    protected ReflectionFragment(String parameterName, String sqlDataType) {
         this(parameterName);
         if (sqlDataType != null) {
             _sqlDataType = TypeMappingsFactory.getInstance().convertStringToSQLType(sqlDataType);
@@ -76,8 +77,29 @@
      * @param args    The method's parameters
      * @return Always returns a PREPARED_STATEMENT_SUB_MARK
      */
-    String getPreparedStatementText(ControlBeanContext context, Method m, Object[] args) {
-        return PREPARED_STATEMENT_SUB_MARK;
+    protected String getPreparedStatementText(ControlBeanContext context, Method m, Object[] args) {
+        // this reflection fragment may resolve to a JdbcControl.ComplexSqlFragment
+        // if so it changes the behavior a bit.
+        Object val = getParameterValue(context, m, args);
+        if (val instanceof JdbcControl.ComplexSqlFragment) {
+            return ((JdbcControl.ComplexSqlFragment)val).getSQL();
+        } else {
+            return PREPARED_STATEMENT_SUB_MARK;
+        }
+    }
+
+    /**
+     * A reflection fragment may evaluate to an JdbcControl.ComplexSqlFragment type,
+     * which requires additional steps to evaluate after reflection.
+     *
+     * @param context Control bean context.
+     * @param m Method.
+     * @param args Method args.
+     * @return true or false.
+     */
+    protected boolean hasComplexValue(ControlBeanContext context, Method m, Object[] args) {
+        Object val = getParameterValue(context, m, args);
+        return val instanceof JdbcControl.ComplexSqlFragment;
     }
 
     /**
@@ -85,21 +107,21 @@
      *
      * @return true
      */
-    boolean hasParamValue() { return true; }
+    protected boolean hasParamValue() { return true; }
 
     /**
      * Get the parameter name (as specified in the SQL statement).
      *
      * @return The parameter name.
      */
-    String getParameterName() { return _parameterName; }
+    protected String getParameterName() { return _parameterName; }
 
     /**
      * Get a copy of the array of parameter name qualifiers.
      *
      * @return An array of parameter name qualifiers.
      */
-    String[] getParameterNameQualifiers() {
+    protected String[] getParameterNameQualifiers() {
         String[] nameQualifiersCopy = new String[_nameQualifiers.length];
         System.arraycopy(_nameQualifiers, 0, nameQualifiersCopy, 0, _nameQualifiers.length);
         return nameQualifiersCopy;
@@ -110,7 +132,7 @@
      *
      * @return The SQL data type for this param.
      */
-    int getParamSqlDataType() { return _sqlDataType; }
+    protected int getParamSqlDataType() { return _sqlDataType; }
 
     /**
      * For JUnit testing.
@@ -127,9 +149,32 @@
      * @param args    Method argument values
      * @return All parameter object values contained within this fragment
      */
-    Object[] getParameterValues(ControlBeanContext context, Method method, Object[] args) {
+    protected Object[] getParameterValues(ControlBeanContext context, Method method, Object[] args) {
 
-        Object value = null;
+        Object value = getParameterValue(context, method, args);
+        if (value instanceof JdbcControl.ComplexSqlFragment) {
+            JdbcControl.SQLParameter[] params = ((JdbcControl.ComplexSqlFragment)value).getParameters();
+            Object[] values = new Object[params.length];
+            for (int i = 0; i < params.length; i++) {
+                values[i] = params[i].value;
+            }
+            return values;
+        }
+        return new Object[]{value};
+    }
+
+    //
+    // /////////////////////////////////////////////// PRIVATE METHODS /////////////////////////////////////////////
+    //
+
+    /**
+     * Get the value from the method param.
+     * @param method
+     * @param args
+     * @return Value of reflected method param.
+     */
+    private Object getParameterValue(ControlBeanContext context, Method method, Object[] args) {
+        Object value;
         try {
             value = context.getParameterValue(method, _nameQualifiers[0], args);
         } catch (IllegalArgumentException iae) {
@@ -140,32 +185,28 @@
             // handle maps, properties, and fields...
             value = extractValue(value, _nameQualifiers[i - 1], _nameQualifiers[i]);
         }
-        return new Object[]{value};
+        return value;
     }
 
-    //
-    // /////////////////////////////////////////////// PRIVATE METHODS /////////////////////////////////////////////
-    //
-
     /**
      * Get the value from the referenced method parameter using java reflection
      *
      * @param aValue
      * @param aName
      * @param bName
-     * @return
+     * @return The value
      */
     private Object extractValue(Object aValue, String aName, String bName) {
 
         Class aClass = aValue.getClass();
-        Object value = null;
+        Object value;
 
         //
         //  a.isB() or a.getB()
         //
         String bNameCapped = Character.toUpperCase(bName.charAt(0)) + bName.substring(1);
         Method getMethod = null;
-        Class retType = null;
+        Class retType;
 
         //
         // try a.isB() first, if found verify that a.isB() returns a boolean value,
@@ -198,6 +239,7 @@
                 }
             }
         } catch (NoSuchMethodException e) {
+            // ignore
         }
 
         //
@@ -206,8 +248,8 @@
         if (getMethod == null) {
             try {
                 getMethod = aClass.getMethod("get" + bNameCapped, (Class[])null);
-                retType = getMethod.getReturnType();
             } catch (NoSuchMethodException e) {
+                // ignore
             }
         }
 
@@ -231,7 +273,9 @@
             value = aClass.getField(bName).get(aValue);
             return value;
         } catch (NoSuchFieldException e) {
+            // ignore
         } catch (IllegalAccessException e) {
+            // ignore
         }
 
         //
@@ -248,11 +292,8 @@
         }
 
         // no other options...
-        if (true) {
-            throw new ControlException("Illegal argument in SQL statement: " + _parameterName.toString()
-                                       + "; unable to find suitable method of retrieving property " + bName.toString()
-                                       + " out of object " + aName.toString() + ".");
-        }
-        return null;
+        throw new ControlException("Illegal argument in SQL statement: " + _parameterName
+                                    + "; unable to find suitable method of retrieving property " + bName
+                                    + " out of object " + aName + ".");
     }
 }

Modified: beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlFragment.java
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlFragment.java?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlFragment.java (original)
+++ beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlFragment.java Tue Mar 14 12:21:45 2006
@@ -45,6 +45,19 @@
     }
 
     /**
+     * True if this fragment evaluates to a JdbcControl.ComplexSqlFragment, i.e.
+     * a value type which needs additional evaluation after reflection.
+     * 
+     * @param context Control bean context.
+     * @param method Method.
+     * @param args Method args.
+     * @return true if evaluates to a complex sql fragment.
+     */
+    protected boolean hasComplexValue(ControlBeanContext context, Method method, Object[] args) {
+        return false;
+    }
+
+    /**
      * Get the SQL data type for the parameter value contained within this fragment.
      * @return The SQL data type for this fragment.
      */

Modified: beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlSubstitutionFragment.java
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlSubstitutionFragment.java?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlSubstitutionFragment.java (original)
+++ beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/parser/SqlSubstitutionFragment.java Tue Mar 14 12:21:45 2006
@@ -22,13 +22,15 @@
 import org.apache.beehive.controls.system.jdbc.TypeMappingsFactory;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Represents a fragement from the SQL annotation's statement member which begins with '{sql:'.
  * Substitution fragements are unique in that they are fully evaluated BEFORE a PreparedStatement
  * is generated.
  * <p/>
- * Supported 'sql:' escapes are subst and fn. subst is the default mode, and will be used if 'sql: ' 
+ * Supported 'sql:' escapes are subst and fn. subst is the default mode, and will be used if 'sql: '
  * is specified.
  *
  * The <tt>fn</tt> variant of this construct has a very ridgid syntax at this point.  It must conform to:
@@ -39,14 +41,16 @@
  *
  * where the '{y}' could also be some literal term.
  */
-public class SqlSubstitutionFragment extends SqlFragmentContainer {
+public final class SqlSubstitutionFragment extends SqlFragmentContainer {
+
+    private boolean _hasParamValue = false;
 
     /**
      * Constructor for subst or function with no param substitution
      *
      * @param child An child which is contained in this fragment.
      */
-    SqlSubstitutionFragment(SqlFragment child) {
+    protected SqlSubstitutionFragment(SqlFragment child) {
         super();
         addChild(child);
     }
@@ -58,7 +62,7 @@
      * @param rf The ReflectionFragment containing the parameter substitution
      * @param lff A LiteralFragment which contains any text which occures after the parameter substitution.
      */
-    SqlSubstitutionFragment(LiteralFragment lf, ReflectionFragment rf, LiteralFragment lff) {
+    protected SqlSubstitutionFragment(LiteralFragment lf, ReflectionFragment rf, LiteralFragment lff) {
         super();
         addChild(lf);
         addChild(rf);
@@ -69,33 +73,61 @@
      * Always true for this fragment type
      * @return true
      */
-    boolean isDynamicFragment() { return true; }
+    protected boolean isDynamicFragment() { return true; }
 
     /**
-     * Always false for this fragment type, since all param values are resolved
-     * before the prepared statement is created.
-     * @return false
+     * Will be true for this fragment type only if one of its children contains
+     * a complex sql fragment.
+     * @return true if there are param values which need to be retrieved.
+     */
+    protected boolean hasParamValue() {
+        return _hasParamValue;
+    }
+
+    /**
+     * Get the parameter values from this fragment and its children.  An SqlSubstitutionFragment
+     * only contains parameters if one of its children has a complex value type.
+     *
+     * @param context A ControlBeanContext instance
+     * @param m The annotated method
+     * @param args The method parameters
+     * @return Array of objects.
      */
-    boolean hasParamValue() { return false; }
+    protected Object[] getParameterValues(ControlBeanContext context, Method m, Object[] args) {
+        ArrayList<Object> paramValues = new ArrayList<Object>();
+        for (SqlFragment frag : _children) {
+            if (frag.hasComplexValue(context, m, args)) {
+                paramValues.addAll(Arrays.asList(frag.getParameterValues(context, m, args)));
+            }
+        }
+        return paramValues.toArray();
+    }
 
     /**
-     * Return the text for a PreparedStatement from this fragment.
+     * Return the text for a PreparedStatement from this fragment. This type of fragment
+     * typically evaluates any reflection parameters at this point.  The exception
+     * is if the reflected result is a ComplexSqlFragment, it that case the sql text
+     * is retrieved from the fragment in this method.  The parameter values are
+     * retrieved in the getParameterValues method of this class.
      *
      * @param context A ControlBeanContext instance
      * @param m The annotated method
      * @param args The method parameters
      * @return A String containing the value of this fragment and its children
      */
-    String getPreparedStatementText(ControlBeanContext context, Method m, Object[] args) {
+    protected String getPreparedStatementText(ControlBeanContext context, Method m, Object[] args) {
 
         StringBuilder sb = new StringBuilder();
         for (SqlFragment frag : _children) {
-            if (frag.hasParamValue()) {
+
+            boolean complexFragment = frag.hasComplexValue(context, m, args);
+            if (frag.hasParamValue() && !complexFragment) {
                 Object[] pValues = frag.getParameterValues(context, m, args);
                 for (Object o : pValues) {
                     sb.append(processSqlParams(o));
                 }
             } else {
+                _hasParamValue |= complexFragment;
                 sb.append(frag.getPreparedStatementText(context, m, args));
             }
         }
@@ -111,7 +143,7 @@
      * seperated by commas.
      *
      * @param value
-     * @return
+     * @return String containing value.
      */
     private String processSqlParams(Object value) {
 

Modified: beehive/trunk/system-controls/test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ResultsTestCtrl.jcx
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ResultsTestCtrl.jcx?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ResultsTestCtrl.jcx (original)
+++ beehive/trunk/system-controls/test/jdbc/controls/org/apache/beehive/controls/system/jdbc/test/results/ResultsTestCtrl.jcx Tue Mar 14 12:21:45 2006
@@ -39,7 +39,7 @@
  * This control is used for unit tests for results returned from the db control.
  */
 @org.apache.beehive.controls.api.bean.ControlExtension
-@JdbcControl.ConnectionDriver(databaseDriverClass="org.apache.derby.jdbc.EmbeddedDriver", databaseURL="jdbc:derby:MyDB")
+@JdbcControl.ConnectionDriver(databaseDriverClass="org.apache.derby.jdbc.EmbeddedDriver", databaseURL="jdbc:derby:MyDB;create=true")
 public interface ResultsTestCtrl extends JdbcControl {
 
     public static class Customer {
@@ -73,6 +73,16 @@
         private int foo;
     }
 
+    public static class ComplexWhereClause
+    {
+        public JdbcControl.ComplexSqlFragment getWhereClause()
+        {
+            JdbcControl.SQLParameter[] params = new JdbcControl.SQLParameter[1];
+            params[0] = new JdbcControl.SQLParameter("tester4");
+            return new JdbcControl.ComplexSqlFragment("where fname = ?", params);
+        }
+    }
+
     public static class BlobInfo {
         private Blob blb;
 
@@ -155,6 +165,14 @@
     //
     @SQL(statement="SELECT * FROM USERS WHERE {sql: where}")
     public ResultSet getJustOneUser(String where) throws SQLException;
+
+    // query using ComplexSqlFragments in a sql: substitution
+    @SQL(statement="SELECT * FROM USERS {sql: cwc.whereClause}")
+    public ResultSet whereClauseSql(ComplexWhereClause cwc);
+
+    // query using ComplexSqlFragments in a parameter substitution
+    @SQL(statement="SELECT * FROM USERS {cwc.whereClause}")
+    public ResultSet whereClauseSub(ComplexWhereClause cwc);
 
     //
     // query returning an array of Object

Modified: beehive/trunk/system-controls/test/jdbc/junitTests/org/apache/beehive/controls/system/jdbc/units/results/DBSingleRowResultsTest.java
URL: http://svn.apache.org/viewcvs/beehive/trunk/system-controls/test/jdbc/junitTests/org/apache/beehive/controls/system/jdbc/units/results/DBSingleRowResultsTest.java?rev=385874&r1=385873&r2=385874&view=diff
==============================================================================
--- beehive/trunk/system-controls/test/jdbc/junitTests/org/apache/beehive/controls/system/jdbc/units/results/DBSingleRowResultsTest.java (original)
+++ beehive/trunk/system-controls/test/jdbc/junitTests/org/apache/beehive/controls/system/jdbc/units/results/DBSingleRowResultsTest.java Tue Mar 14 12:21:45 2006
@@ -118,6 +118,32 @@
     }
 
     /**
+     * test query which does reflection on a property which contains an ComplexSqlFragment type.
+     * @throws Exception
+     */
+    public void testSqlEscapeComplexWhere() throws Exception {
+        ResultSet rs = testCtrl.whereClauseSql(new ResultsTestCtrl.ComplexWhereClause());
+        assertNotNull(rs);
+        rs.next();
+        String name = rs.getString("FNAME");
+        assertEquals(name, "tester4");
+        rs.close();
+    }
+
+    /**
+     * test query which does reflection on a property which contains an ComplexSqlFragment type.
+     * @throws Exception
+     */
+    public void testComplexWhere() throws Exception {
+        ResultSet rs = testCtrl.whereClauseSub(new ResultsTestCtrl.ComplexWhereClause());
+        assertNotNull(rs);
+        rs.next();
+        String name = rs.getString("FNAME");
+        assertEquals(name, "tester4");
+        rs.close();
+    }
+
+    /**
      * param sub from object getter method
      * @throws Exception
      */