1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbutils;
18  
19  import java.beans.IndexedPropertyDescriptor;
20  import java.beans.PropertyDescriptor;
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Proxy;
24  import java.sql.ParameterMetaData;
25  import java.sql.PreparedStatement;
26  import java.sql.SQLException;
27  import java.sql.Types;
28  import java.util.Arrays;
29  
30  import junit.framework.TestCase;
31  
32  public class QueryRunnerTest extends TestCase {
33      QueryRunner runner;
34      PreparedStatement stmt;
35      
36      static final Method getParameterCount, getParameterType, getParameterMetaData;
37      static {
38          try {
39              getParameterCount = ParameterMetaData.class.getMethod("getParameterCount", new Class[0]);
40              getParameterType = ParameterMetaData.class.getMethod("getParameterType", new Class[]{int.class});
41              getParameterMetaData = PreparedStatement.class.getMethod("getParameterMetaData", new Class[0]);
42          } catch (Exception e) {
43              throw new RuntimeException(e);
44          }
45      }
46      
47      public void setUp() {
48          runner = new QueryRunner();
49          stmt = fakePreparedStatement();
50      }
51      
52      public void testFillStatementWithNull() throws Exception {
53          stmt = fakeFillablePreparedStatement(false, new int[] {Types.VARCHAR, Types.BIGINT});
54          runner.fillStatement(stmt, new Object[] { null, null }); 
55      }
56      
57      public void testFillStatementWithNullOracle() throws Exception {
58          stmt = fakeFillablePreparedStatement(true, new int[] {Types.VARCHAR, Types.BIGINT});
59          runner.fillStatement(stmt, new Object[] { null, null });
60      }
61  
62      private PreparedStatement fakeFillablePreparedStatement(final boolean simulateOracle, final int[] types) throws NoSuchMethodException {
63          // prepare a mock ParameterMetaData and a mock PreparedStatement to return the PMD
64          final ParameterMetaData pmd = mockParameterMetaData(simulateOracle,types);
65          InvocationHandler stmtHandler = new InvocationHandler() {
66              public Object invoke(Object proxy, Method method, Object[] args)
67                      throws Throwable {
68                  if (getParameterMetaData.equals(method)) {
69                      return pmd;
70                  }
71                  return null;
72              }
73          };
74          return ProxyFactory.instance().createPreparedStatement(stmtHandler);
75      }
76  
77      private ParameterMetaData mockParameterMetaData(final boolean simulateOracle, final int[] types) {
78          InvocationHandler pmdHandler = new InvocationHandler() {
79              public Object invoke(Object proxy, Method method, Object[] args)
80                      throws Throwable {
81                  if (getParameterCount.equals(method)) {
82                      return new Integer(types.length);
83                  }
84                  if (getParameterType.equals(method)) {
85                      if (simulateOracle) throw new SQLException("Oracle fails when you call getParameterType");
86                      int arg = ((Integer)args[0]).intValue();
87                      return new Integer(types[arg-1]);
88                  }
89                  return null;
90              }
91          };
92          
93          return (ParameterMetaData) Proxy.newProxyInstance(
94                  pmdHandler.getClass().getClassLoader(),
95                  new Class[] {ParameterMetaData.class},
96                  pmdHandler);
97      }
98      
99      public void testFillStatementWithBean() throws SQLException {
100         TestBean tb = new TestBean();
101         tb.setOne("uno");
102         tb.setTwo("dos");
103         tb.setThree("tres");
104         NoOpFillStatement fakeQueryRunner = new NoOpFillStatement();
105         fakeQueryRunner.fillStatementWithBean(stmt, tb, new String[] {"three", "two", "one"});
106         String[] expected = new String[] {"tres", "dos", "uno"};
107         assertArrayEquals("Statement filled with incorrect parameters", expected, fakeQueryRunner.params);
108     }
109 
110     private PreparedStatement fakePreparedStatement() {
111         InvocationHandler noOpHandler = new InvocationHandler() {
112             public Object invoke(Object proxy, Method method, Object[] args)
113                     throws Throwable {
114                 return null;
115             }
116         };
117         PreparedStatement stmt = ProxyFactory.instance().createPreparedStatement(noOpHandler);
118         return stmt;
119     }
120     
121     public void testFillStatementWithBeanErrorNoReadMethod() throws Exception {
122         TestBean tb = new TestBean();
123         PropertyDescriptor noReadMethod = new PropertyDescriptor("one", TestBean.class, null, "setOne");
124         
125         PropertyDescriptor properties[] = new PropertyDescriptor[] { noReadMethod };
126         try {
127             runner.fillStatementWithBean(stmt, tb, properties);
128             fail("Expected RuntimeException: tried to use a property with no read method");
129         } catch (RuntimeException expected) {}
130     }
131     
132     public void testFillStatementWithBeanErrorBadReadMethod() throws Exception {
133         PropertyDescriptor badReadMethod = new IndexedPropertyDescriptor("indexed", getClass(), null, null, "getIndexed", null) {
134             public synchronized Method getReadMethod() {
135                 return super.getIndexedReadMethod();
136             }
137         };
138         PropertyDescriptor properties[] = new PropertyDescriptor[] { badReadMethod };
139         try {
140             runner.fillStatementWithBean(stmt, this, properties);
141             fail("Expected RuntimeException: tried to use a property with no no-arg read method");
142         } catch (RuntimeException expected) {}
143     }
144     
145     public void testFillStatementWithBeanErrorReadMethodThrows() throws Exception {
146         PropertyDescriptor badReadMethod = new PropertyDescriptor("throwsException", getClass(), "getThrowsException", null);
147         PropertyDescriptor properties[] = new PropertyDescriptor[] { badReadMethod };
148         try {
149             runner.fillStatementWithBean(stmt, this, properties);
150             fail("Expected RuntimeException: tried to call a method that throws");
151         } catch (RuntimeException expected) {}
152     }
153     
154     public void testFillStatementWithBeanErrorReadMethodPrivate() throws Exception {
155         getPrivate();
156         PropertyDescriptor badReadMethod = new BadPrivatePropertyDescriptor();
157         PropertyDescriptor properties[] = new PropertyDescriptor[] { badReadMethod };
158         try {
159             runner.fillStatementWithBean(stmt, this, properties);
160             fail("Expected RuntimeException: tried to call a private method");
161         } catch (RuntimeException expected) {}
162     }
163     
164     class BadPrivatePropertyDescriptor extends PropertyDescriptor {
165         Method getPrivate;
166         BadPrivatePropertyDescriptor() throws Exception {
167             super("throwsException", QueryRunnerTest.class, "getThrowsException", null);
168             getPrivate = QueryRunnerTest.class.getDeclaredMethod("getPrivate", new Class[0]);
169         }
170         
171         public synchronized Method getReadMethod() {
172             if (getPrivate == null) return super.getReadMethod();
173             return getPrivate;
174         }
175     }
176     
177     public void testRethrowNullMessage() {
178         // DBUTILS-40
179         SQLException sqe = new SQLException((String)null);
180         QueryRunner qr = new QueryRunner();
181         try {
182             qr.rethrow(sqe, "foo", new Object[] {"bar"});
183             fail("rethrow didn't throw");
184         } catch (SQLException expected) {}
185     }
186     
187     // indexed bean property
188     public String getIndexed(int index) {
189         return null;
190     }
191     
192     public String getThrowsException() {
193         throw new RuntimeException("this getter always throws an exception");
194     }
195     
196     private String getPrivate() {
197         return null;
198     }
199     
200     private void assertArrayEquals(String message, Object[] expected, Object[] actual) {
201         assertEquals(message, Arrays.asList(expected).toString(), Arrays.asList(actual).toString());
202         assertEquals(message, expected.length, actual.length);
203     }
204     
205     
206     private class NoOpFillStatement extends QueryRunner {
207         Object[] params;
208         public void fillStatement(PreparedStatement stmt, Object[] params)
209                 throws SQLException {
210             this.params = params;
211         }
212     }
213 
214 }