001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.dbutils;
018    
019    import java.beans.IndexedPropertyDescriptor;
020    import java.beans.PropertyDescriptor;
021    import java.lang.reflect.InvocationHandler;
022    import java.lang.reflect.Method;
023    import java.lang.reflect.Proxy;
024    import java.sql.ParameterMetaData;
025    import java.sql.PreparedStatement;
026    import java.sql.SQLException;
027    import java.sql.Types;
028    import java.util.Arrays;
029    
030    import junit.framework.TestCase;
031    
032    public class QueryRunnerTest extends TestCase {
033        QueryRunner runner;
034        PreparedStatement stmt;
035        
036        static final Method getParameterCount, getParameterType, getParameterMetaData;
037        static {
038            try {
039                getParameterCount = ParameterMetaData.class.getMethod("getParameterCount", new Class[0]);
040                getParameterType = ParameterMetaData.class.getMethod("getParameterType", new Class[]{int.class});
041                getParameterMetaData = PreparedStatement.class.getMethod("getParameterMetaData", new Class[0]);
042            } catch (Exception e) {
043                throw new RuntimeException(e);
044            }
045        }
046        
047        public void setUp() {
048            runner = new QueryRunner();
049            stmt = fakePreparedStatement();
050        }
051        
052        public void testFillStatementWithNull() throws Exception {
053            stmt = fakeFillablePreparedStatement(false, new int[] {Types.VARCHAR, Types.BIGINT});
054            runner.fillStatement(stmt, new Object[] { null, null }); 
055        }
056        
057        public void testFillStatementWithNullOracle() throws Exception {
058            stmt = fakeFillablePreparedStatement(true, new int[] {Types.VARCHAR, Types.BIGINT});
059            runner.fillStatement(stmt, new Object[] { null, null });
060        }
061    
062        private PreparedStatement fakeFillablePreparedStatement(final boolean simulateOracle, final int[] types) throws NoSuchMethodException {
063            // prepare a mock ParameterMetaData and a mock PreparedStatement to return the PMD
064            final ParameterMetaData pmd = mockParameterMetaData(simulateOracle,types);
065            InvocationHandler stmtHandler = new InvocationHandler() {
066                public Object invoke(Object proxy, Method method, Object[] args)
067                        throws Throwable {
068                    if (getParameterMetaData.equals(method)) {
069                        return pmd;
070                    }
071                    return null;
072                }
073            };
074            return ProxyFactory.instance().createPreparedStatement(stmtHandler);
075        }
076    
077        private ParameterMetaData mockParameterMetaData(final boolean simulateOracle, final int[] types) {
078            InvocationHandler pmdHandler = new InvocationHandler() {
079                public Object invoke(Object proxy, Method method, Object[] args)
080                        throws Throwable {
081                    if (getParameterCount.equals(method)) {
082                        return new Integer(types.length);
083                    }
084                    if (getParameterType.equals(method)) {
085                        if (simulateOracle) throw new SQLException("Oracle fails when you call getParameterType");
086                        int arg = ((Integer)args[0]).intValue();
087                        return new Integer(types[arg-1]);
088                    }
089                    return null;
090                }
091            };
092            
093            return (ParameterMetaData) Proxy.newProxyInstance(
094                    pmdHandler.getClass().getClassLoader(),
095                    new Class[] {ParameterMetaData.class},
096                    pmdHandler);
097        }
098        
099        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    }