You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beehive.apache.org by ek...@apache.org on 2005/05/13 19:16:33 UTC

svn commit: r170058 - /incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs

Author: ekoneil
Date: Fri May 13 10:16:31 2005
New Revision: 170058

URL: http://svn.apache.org/viewcvs?rev=170058&view=rev
Log:
Fix for JIRA 714 with a patch from Chad Schoettger.

This fixes the logging in onAcquire and onRelease to be consistent.

BB: self
DRT: system-controls pass


Modified:
    incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs

Modified: incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs
URL: http://svn.apache.org/viewcvs/incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs?rev=170058&r1=170057&r2=170058&view=diff
==============================================================================
--- incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs (original)
+++ incubator/beehive/trunk/system-controls/src/jdbc/org/apache/beehive/controls/system/jdbc/JdbcControlImpl.jcs Fri May 13 10:16:31 2005
@@ -1,490 +1,495 @@
-/*
- * Copyright 2005 The Apache Software Foundation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * $Header:$
- */
-package org.apache.beehive.controls.system.jdbc;
-
-import org.apache.beehive.controls.api.ControlException;
-import org.apache.beehive.controls.api.bean.ControlImplementation;
-import org.apache.beehive.controls.api.bean.Extensible;
-import org.apache.beehive.controls.api.context.ControlBeanContext;
-import org.apache.beehive.controls.api.context.ResourceContext;
-import org.apache.beehive.controls.api.context.ResourceContext.ResourceEvents;
-import org.apache.beehive.controls.api.events.EventHandler;
-import org.apache.log4j.Logger;
-import org.apache.beehive.controls.system.jdbc.parser.SqlParser;
-import org.apache.beehive.controls.system.jdbc.parser.SqlStatement;
-
-import javax.naming.NamingException;
-import javax.naming.Context;
-import javax.sql.DataSource;
-import java.lang.reflect.Method;
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Vector;
-
-/**
- * The implementation class for the database controller.
- */
-@ControlImplementation
-public class JdbcControlImpl implements JdbcControl, Extensible, java.io.Serializable {
-
-    //
-    // contexts provided by the beehive controls runtime
-    //
-    @org.apache.beehive.controls.api.context.Context
-    protected ControlBeanContext _context;
-    @org.apache.beehive.controls.api.context.Context
-    protected ResourceContext _resourceContext;
-
-    protected transient Connection _connection;
-    protected transient ConnectionDataSource _connectionDataSource;
-    protected transient DataSource _dataSource;
-    protected transient ConnectionDriver _connectionDriver;
-
-    private transient Calendar _cal;
-    private transient Vector<PreparedStatement> _resources = new Vector<PreparedStatement>();
-
-    private static final String EMPTY_STRING = "";
-    private static final Logger logger = Logger.getLogger(JdbcControlImpl.class);
-    private static final ResultSetMapper DEFAULT_MAPPER = new DefaultObjectResultSetMapper();
-    private static final SqlParser _sqlParser = new SqlParser();
-
-    protected static final HashMap<Class, ResultSetMapper> _resultMappers = new HashMap<Class, ResultSetMapper>();
-    protected static Class<?> _xmlObjectClass;
-
-    //
-    // initialize the result mapper table
-    //
-    static {
-        _resultMappers.put(ResultSet.class, new DefaultResultSetMapper());
-        _resultMappers.put(Iterator.class, new DefaultIteratorResultSetMapper());
-
-        try {
-            _xmlObjectClass = Class.forName("org.apache.xmlbeans.XmlObject");
-            _resultMappers.put(_xmlObjectClass, new DefaultXmlObjectResultSetMapper());
-        } catch (ClassNotFoundException e) {
-            // noop: OK if not found, just can't support mapping to an XmlObject
-        }
-    }
-
-    /**
-     * Constructor
-     */
-    public JdbcControlImpl() { }
-
-    /**
-     * Invoked by the controls runtime when a new instance of this class is aquired by the runtime
-     */
-    @EventHandler(field = "_resourceContext", eventSet = ResourceEvents.class, eventName = "onAcquire")
-    public void onAquire() {
-        try {
-            getConnection();
-        } catch (SQLException se) {
-            throw new ControlException("SQL Exception while attempting to connect to database.", se);
-        }
-    }
-
-    /**
-     * Invoked by the controls runtime when an instance of this class is released by the runtime
-     */
-    @EventHandler(field = "_resourceContext", eventSet = ResourceContext.ResourceEvents.class, eventName = "onRelease")
-    public void onRelease() {
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("onRelease() invoked.");
-        }
-
-        for (PreparedStatement ps : _resources) {
-            try {
-                ps.close();
-            } catch (SQLException sqe) {
-            }
-        }
-        _resources.clear();
-
-        if (_connection != null) {
-            try {
-                _connection.close();
-            } catch (SQLException e) {
-                throw new ControlException("SQL Exception while attempting to close database connection.", e);
-            }
-        }
-
-        _connection = null;
-        _connectionDataSource = null;
-        _connectionDriver = null;
-    }
-
-    /**
-     * Returns a database connection to the server associated with the control.
-     * The connection type is specified by a ConnectionDataSource or ConnectionDriver annotation on the control class
-     * which extends this control.
-     * <p/>
-     * It is typically not necessary to call this method when using the control.
-     */
-    public Connection getConnection() throws SQLException {
-
-        if (_connection == null) {
-
-            _connectionDataSource = _context.getControlPropertySet(ConnectionDataSource.class);
-            _connectionDriver = _context.getControlPropertySet(ConnectionDriver.class);
-            final ConnectionOptions connectionOptions = _context.getControlPropertySet(ConnectionOptions.class);
-
-            if (_connectionDataSource != null && _connectionDataSource.jndiName() != null) {
-                _connection = getConnectionFromDataSource(_connectionDataSource.jndiName(),
-                                                          _connectionDataSource.jndiContextFactory());
-
-            } else if (_connectionDriver != null && _connectionDriver.databaseDriverClass() != null) {
-                _connection = getConnectionFromDriverManager(_connectionDriver.databaseDriverClass(),
-                                                             _connectionDriver.databaseURL(),
-                                                             _connectionDriver.userName(),
-                                                             _connectionDriver.password(),
-                                                             _connectionDriver.properties());
-            } else {
-                throw new ControlException("no @\'" + ConnectionDataSource.class.getName()
-                                           + "\' or \'" + ConnectionDriver.class.getName() + "\' property found.");
-            }
-
-            //
-            // set any specifed connection options
-            //
-            if (connectionOptions != null) {
-
-                if (_connection.isReadOnly() != connectionOptions.readOnly()) {
-                    _connection.setReadOnly(connectionOptions.readOnly());
-                }
-
-                DatabaseMetaData dbMetadata = _connection.getMetaData();
-
-                final HoldabilityType holdability = connectionOptions.resultSetHoldability();
-                if (holdability != HoldabilityType.DRIVER_DEFAULT) {
-                    if (dbMetadata.supportsResultSetHoldability(holdability.getHoldability())) {
-                        _connection.setHoldability(holdability.getHoldability());
-                    } else {
-                        throw new ControlException("Database does not support ResultSet holdability type: "
-                                                   + holdability.toString());
-                    }
-                }
-
-                setTypeMappers(connectionOptions.typeMappers());
-            }
-        }
-
-        return _connection;
-    }
-
-
-    /**
-     * Called by the Controls runtime to handle calls to methods of an extensible control.
-     *
-     * @param method The extended operation that was called.
-     * @param args   Parameters of the operation.
-     * @return The value that should be returned by the operation.
-     * @throws Throwable any exception declared on the extended operation may be
-     *                   thrown.  If a checked exception is thrown from the implementation that is not declared
-     *                   on the original interface, it will be wrapped in a ControlException.
-     */
-    public Object invoke(Method method, Object[] args) throws Throwable {
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Enter: invoke()");
-        }
-        assert _connection.isClosed() == false : "invoke(): JDBC Connection has been closed!!!!";
-        return execPreparedStatement(method, args);
-    }
-
-    /**
-     * Sets the calendar used when working with time/date types
-     */
-    public void setDataSourceCalendar(Calendar cal) {
-        _cal = (Calendar) cal.clone();
-    }
-
-    /**
-     * Returns the calendar used when working with time/date types.
-     *
-     * @return
-     */
-    public Calendar getDataSourceCalendar() {
-        return _cal;
-    }
-
-
-// /////////////////////////////////////////// Protected Methods ////////////////////////////////////////////
-
-
-    /**
-     * Create and exec the query
-     *
-     * @param method
-     * @param args
-     * @return
-     * @throws Throwable
-     */
-    protected Object execPreparedStatement(Method method, Object[] args)
-            throws Throwable {
-
-        final SQL methodSQL = (SQL) _context.getMethodPropertySet(method, SQL.class);
-        if (methodSQL == null || methodSQL.statement() == null) {
-            throw new ControlException("Method " + method.getName() + " is missing @SQL annotation");
-        }
-
-        setTypeMappers(methodSQL.typeMappersOverride());
-
-        //
-        // build the statement and execute it
-        //
-
-        PreparedStatement ps = null;
-        try {
-            Class returnType = method.getReturnType();
-
-            SqlStatement sqlStatement = _sqlParser.parse(methodSQL.statement());
-            ps = sqlStatement.createPreparedStatement(_context, _connection, _cal, method, args);
-
-            if (logger.isInfoEnabled()) {
-                logger.info("PreparedStatement: "
-                            + sqlStatement.createPreparedStatementString(_context, _connection,  method, args));
-            }
-
-            //
-            // special processing for batch updates
-            //
-            if (sqlStatement.isBatchUpdate()) {
-                return ps.executeBatch();
-            }
-
-            //
-            // execute the statement
-            //
-            boolean hasResults = ps.execute();
-
-
-            //
-            // callable statement processing
-            //
-            if (sqlStatement.isCallableStatement()) {
-                SQLParameter[] params = (SQLParameter[]) args[0];
-                for (int i = 0; i < params.length; i++) {
-                    if (params[i].dir != SQLParameter.IN) {
-                        params[i].value = ((CallableStatement) ps).getObject(i + 1);
-                    }
-                }
-                return null;
-            }
-
-
-            //
-            // process returned data
-            //
-            ResultSet rs = null;
-            int updateCount = ps.getUpdateCount();
-
-            if (hasResults) {
-                rs = ps.getResultSet();
-            }
-
-            if (sqlStatement.getsGeneratedKeys()) {
-                rs = ps.getGeneratedKeys();
-                hasResults = true;
-            }
-
-            if (!hasResults && updateCount > -1) {
-                boolean moreResults = ps.getMoreResults();
-                int tempUpdateCount = ps.getUpdateCount();
-
-                while ((moreResults && rs == null) || tempUpdateCount > -1) {
-                    if (moreResults) {
-                        rs = ps.getResultSet();
-                        hasResults = true;
-                        moreResults = false;
-                        tempUpdateCount = -1;
-                    } else {
-                        moreResults = ps.getMoreResults();
-                        tempUpdateCount = ps.getUpdateCount();
-                    }
-                }
-            }
-
-            Object returnObject = null;
-            if (hasResults) {
-
-                //
-                // if a result set mapper was specified in the methods annotation, use it
-                // otherwise find the mapper for the return type in the hashmap
-                //
-                final Class resultSetMapperClass = methodSQL.resultSetMapper();
-                final ResultSetMapper rsm;
-                if (!UndefinedResultSetMapper.class.isAssignableFrom(resultSetMapperClass)) {
-                    if (ResultSetMapper.class.isAssignableFrom(resultSetMapperClass)) {
-                        rsm = (ResultSetMapper) resultSetMapperClass.newInstance();
-                    } else {
-                        throw new ControlException("Result set mappers must be subclasses of ResultSetMapper.class!");
-                    }
-                } else {
-                    if (_resultMappers.containsKey(returnType)) {
-                        rsm = _resultMappers.get(returnType);
-                    } else {
-                        if (_xmlObjectClass != null && _xmlObjectClass.isAssignableFrom(returnType)) {
-                            rsm = _resultMappers.get(_xmlObjectClass);
-                        } else {
-                            rsm = DEFAULT_MAPPER;
-                        }
-                    }
-                }
-
-                returnObject = rsm.mapToResultType(_context, method, rs, _cal);
-                if (rsm.canCloseResultSet() == false) {
-                    _resources.add(ps);
-                }
-
-                //
-                // empty ResultSet
-                //
-            } else {
-                if (returnType.equals(Void.TYPE)) {
-                    returnObject = null;
-                } else if (returnType.equals(Integer.TYPE)) {
-                    returnObject = new Integer(updateCount);
-                } else if (!sqlStatement.isCallableStatement()) {
-                    throw new ControlException("Method " + method.getName() + "is DML but does not return void or int");
-                }
-            }
-            return returnObject;
-
-        } finally {
-            // Keep statements open that have in-use result sets
-            if (ps != null && !_resources.contains(ps)) {
-                ps.close();
-            }
-        }
-    }
-
-// /////////////////////////////////////////// Private Methods ////////////////////////////////////////////
-
-    /**
-     * Get a connection from a DataSource.
-     *
-     * @param jndiName    Specifed in the subclasse's ConnectionDataSource annotation
-     * @param jndiFactory Specified in the subclasse's ConnectionDataSource Annotation.
-     * @return null if a connection cannot be established
-     * @throws SQLException
-     */
-    private Connection getConnectionFromDataSource(String jndiName,
-                                                   Class<? extends JdbcControl.JndiContextFactory> jndiFactory)
-            throws SQLException
-    {
-
-        Connection con = null;
-        try {
-            JndiContextFactory jf = (JndiContextFactory) jndiFactory.newInstance();
-            Context jndiContext = jf.getContext();
-            _dataSource = (DataSource) jndiContext.lookup(jndiName);
-            con = _dataSource.getConnection();
-        } catch (IllegalAccessException iae) {
-            throw new ControlException("IllegalAccessException:", iae);
-        } catch (InstantiationException ie) {
-            throw new ControlException("InstantiationException:", ie);
-        } catch (NamingException ne) {
-            throw new ControlException("NamingException:", ne);
-        }
-        return con;
-    }
-
-    /**
-     * Get a JDBC connection from the DriverManager.
-     *
-     * @param dbDriverClassName Specified in the subclasse's ConnectionDriver annotation.
-     * @param dbUrlStr          Specified in the subclasse's ConnectionDriver annotation.
-     * @param userName          Specified in the subclasse's ConnectionDriver annotation.
-     * @param password          Specified in the subclasse's ConnectionDriver annotation.
-     * @return null if a connection cannot be established.
-     * @throws SQLException
-     */
-    private Connection getConnectionFromDriverManager(String dbDriverClassName, String dbUrlStr,
-                                                      String userName, String password, String propertiesString)
-            throws SQLException
-    {
-
-        Connection con = null;
-        try {
-            Class.forName(dbDriverClassName);
-            if (!EMPTY_STRING.equals(userName)) {
-                con = DriverManager.getConnection(dbUrlStr, userName, password);
-            } else if (!EMPTY_STRING.equals(propertiesString)) {
-                Properties props = parseProperties(propertiesString);
-                if (props == null) {
-                    throw new ControlException("Invalid properties annotation value: " + propertiesString);
-                }
-                con = DriverManager.getConnection(dbUrlStr, props);
-            } else {
-                con = DriverManager.getConnection(dbUrlStr);
-            }
-        } catch (ClassNotFoundException e) {
-            throw new ControlException("Database driver class not found!", e);
-        }
-        return con;
-    }
-
-    /**
-     * Parse the propertiesString into a Properties object.  The string must have the format of:
-     * propertyName=propertyValue;propertyName=propertyValue;...
-     *
-     * @param propertiesString
-     * @return A Properties instance or null if parse fails
-     */
-    private Properties parseProperties(String propertiesString) {
-        Properties properties = null;
-        String[] propPairs = propertiesString.split(";");
-        if (propPairs.length > 0) {
-            properties = new Properties();
-            for (String propPair : propPairs) {
-                int eq = propPair.indexOf('=');
-                assert eq > -1 : "Invalid properties syntax: " + propertiesString;
-                properties.put(propPair.substring(0, eq), propPair.substring(eq + 1, propPair.length()));
-            }
-        }
-        return properties;
-    }
-
-    /**
-     * Set any custom type mappers specifed in the annotation for the connection.  Used for mapping SQL UDTs to
-     * java classes.
-     *
-     * @param typeMappers An array of TypeMapper.
-     */
-    private void setTypeMappers(TypeMapper[] typeMappers) throws SQLException {
-
-        if (typeMappers.length > 0) {
-            Map<String, Class<?>> mappers = _connection.getTypeMap();
-            for (TypeMapper t : typeMappers) {
-                mappers.put(t.UDTName(), t.mapperClass());
-            }
-            _connection.setTypeMap(mappers);
-        }
-    }
-}
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * $Header:$
+ */
+package org.apache.beehive.controls.system.jdbc;
+
+import org.apache.beehive.controls.api.ControlException;
+import org.apache.beehive.controls.api.bean.ControlImplementation;
+import org.apache.beehive.controls.api.bean.Extensible;
+import org.apache.beehive.controls.api.context.ControlBeanContext;
+import org.apache.beehive.controls.api.context.ResourceContext;
+import org.apache.beehive.controls.api.context.ResourceContext.ResourceEvents;
+import org.apache.beehive.controls.api.events.EventHandler;
+import org.apache.log4j.Logger;
+import org.apache.beehive.controls.system.jdbc.parser.SqlParser;
+import org.apache.beehive.controls.system.jdbc.parser.SqlStatement;
+
+import javax.naming.NamingException;
+import javax.naming.Context;
+import javax.sql.DataSource;
+import java.lang.reflect.Method;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * The implementation class for the database controller.
+ */
+@ControlImplementation
+public class JdbcControlImpl implements JdbcControl, Extensible, java.io.Serializable {
+
+    //
+    // contexts provided by the beehive controls runtime
+    //
+    @org.apache.beehive.controls.api.context.Context
+    protected ControlBeanContext _context;
+    @org.apache.beehive.controls.api.context.Context
+    protected ResourceContext _resourceContext;
+
+    protected transient Connection _connection;
+    protected transient ConnectionDataSource _connectionDataSource;
+    protected transient DataSource _dataSource;
+    protected transient ConnectionDriver _connectionDriver;
+
+    private transient Calendar _cal;
+    private transient Vector<PreparedStatement> _resources = new Vector<PreparedStatement>();
+
+    private static final String EMPTY_STRING = "";
+    private static final Logger logger = Logger.getLogger(JdbcControlImpl.class);
+    private static final ResultSetMapper DEFAULT_MAPPER = new DefaultObjectResultSetMapper();
+    private static final SqlParser _sqlParser = new SqlParser();
+
+    protected static final HashMap<Class, ResultSetMapper> _resultMappers = new HashMap<Class, ResultSetMapper>();
+    protected static Class<?> _xmlObjectClass;
+
+    //
+    // initialize the result mapper table
+    //
+    static {
+        _resultMappers.put(ResultSet.class, new DefaultResultSetMapper());
+        _resultMappers.put(Iterator.class, new DefaultIteratorResultSetMapper());
+
+        try {
+            _xmlObjectClass = Class.forName("org.apache.xmlbeans.XmlObject");
+            _resultMappers.put(_xmlObjectClass, new DefaultXmlObjectResultSetMapper());
+        } catch (ClassNotFoundException e) {
+            // noop: OK if not found, just can't support mapping to an XmlObject
+        }
+    }
+
+    /**
+     * Constructor
+     */
+    public JdbcControlImpl() { }
+
+    /**
+     * Invoked by the controls runtime when a new instance of this class is aquired by the runtime
+     */
+    @EventHandler(field = "_resourceContext", eventSet = ResourceEvents.class, eventName = "onAcquire")
+    public void onAquire() {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Enter: onAquire()");
+        }
+
+        try {
+            getConnection();
+        } catch (SQLException se) {
+            throw new ControlException("SQL Exception while attempting to connect to database.", se);
+        }
+    }
+
+    /**
+     * Invoked by the controls runtime when an instance of this class is released by the runtime
+     */
+    @EventHandler(field = "_resourceContext", eventSet = ResourceContext.ResourceEvents.class, eventName = "onRelease")
+    public void onRelease() {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Enter: onRelease()");
+        }
+
+        for (PreparedStatement ps : _resources) {
+            try {
+                ps.close();
+            } catch (SQLException sqe) {
+            }
+        }
+        _resources.clear();
+
+        if (_connection != null) {
+            try {
+                _connection.close();
+            } catch (SQLException e) {
+                throw new ControlException("SQL Exception while attempting to close database connection.", e);
+            }
+        }
+
+        _connection = null;
+        _connectionDataSource = null;
+        _connectionDriver = null;
+    }
+
+    /**
+     * Returns a database connection to the server associated with the control.
+     * The connection type is specified by a ConnectionDataSource or ConnectionDriver annotation on the control class
+     * which extends this control.
+     * <p/>
+     * It is typically not necessary to call this method when using the control.
+     */
+    public Connection getConnection() throws SQLException {
+
+        if (_connection == null) {
+
+            _connectionDataSource = _context.getControlPropertySet(ConnectionDataSource.class);
+            _connectionDriver = _context.getControlPropertySet(ConnectionDriver.class);
+            final ConnectionOptions connectionOptions = _context.getControlPropertySet(ConnectionOptions.class);
+
+            if (_connectionDataSource != null && _connectionDataSource.jndiName() != null) {
+                _connection = getConnectionFromDataSource(_connectionDataSource.jndiName(),
+                                                          _connectionDataSource.jndiContextFactory());
+
+            } else if (_connectionDriver != null && _connectionDriver.databaseDriverClass() != null) {
+                _connection = getConnectionFromDriverManager(_connectionDriver.databaseDriverClass(),
+                                                             _connectionDriver.databaseURL(),
+                                                             _connectionDriver.userName(),
+                                                             _connectionDriver.password(),
+                                                             _connectionDriver.properties());
+            } else {
+                throw new ControlException("no @\'" + ConnectionDataSource.class.getName()
+                                           + "\' or \'" + ConnectionDriver.class.getName() + "\' property found.");
+            }
+
+            //
+            // set any specifed connection options
+            //
+            if (connectionOptions != null) {
+
+                if (_connection.isReadOnly() != connectionOptions.readOnly()) {
+                    _connection.setReadOnly(connectionOptions.readOnly());
+                }
+
+                DatabaseMetaData dbMetadata = _connection.getMetaData();
+
+                final HoldabilityType holdability = connectionOptions.resultSetHoldability();
+                if (holdability != HoldabilityType.DRIVER_DEFAULT) {
+                    if (dbMetadata.supportsResultSetHoldability(holdability.getHoldability())) {
+                        _connection.setHoldability(holdability.getHoldability());
+                    } else {
+                        throw new ControlException("Database does not support ResultSet holdability type: "
+                                                   + holdability.toString());
+                    }
+                }
+
+                setTypeMappers(connectionOptions.typeMappers());
+            }
+        }
+
+        return _connection;
+    }
+
+
+    /**
+     * Called by the Controls runtime to handle calls to methods of an extensible control.
+     *
+     * @param method The extended operation that was called.
+     * @param args   Parameters of the operation.
+     * @return The value that should be returned by the operation.
+     * @throws Throwable any exception declared on the extended operation may be
+     *                   thrown.  If a checked exception is thrown from the implementation that is not declared
+     *                   on the original interface, it will be wrapped in a ControlException.
+     */
+    public Object invoke(Method method, Object[] args) throws Throwable {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Enter: invoke()");
+        }
+        assert _connection.isClosed() == false : "invoke(): JDBC Connection has been closed!!!!";
+        return execPreparedStatement(method, args);
+    }
+
+    /**
+     * Sets the calendar used when working with time/date types
+     */
+    public void setDataSourceCalendar(Calendar cal) {
+        _cal = (Calendar) cal.clone();
+    }
+
+    /**
+     * Returns the calendar used when working with time/date types.
+     *
+     * @return
+     */
+    public Calendar getDataSourceCalendar() {
+        return _cal;
+    }
+
+
+// /////////////////////////////////////////// Protected Methods ////////////////////////////////////////////
+
+
+    /**
+     * Create and exec the query
+     *
+     * @param method
+     * @param args
+     * @return
+     * @throws Throwable
+     */
+    protected Object execPreparedStatement(Method method, Object[] args)
+            throws Throwable {
+
+        final SQL methodSQL = (SQL) _context.getMethodPropertySet(method, SQL.class);
+        if (methodSQL == null || methodSQL.statement() == null) {
+            throw new ControlException("Method " + method.getName() + " is missing @SQL annotation");
+        }
+
+        setTypeMappers(methodSQL.typeMappersOverride());
+
+        //
+        // build the statement and execute it
+        //
+
+        PreparedStatement ps = null;
+        try {
+            Class returnType = method.getReturnType();
+
+            SqlStatement sqlStatement = _sqlParser.parse(methodSQL.statement());
+            ps = sqlStatement.createPreparedStatement(_context, _connection, _cal, method, args);
+
+            if (logger.isInfoEnabled()) {
+                logger.info("PreparedStatement: "
+                            + sqlStatement.createPreparedStatementString(_context, _connection,  method, args));
+            }
+
+            //
+            // special processing for batch updates
+            //
+            if (sqlStatement.isBatchUpdate()) {
+                return ps.executeBatch();
+            }
+
+            //
+            // execute the statement
+            //
+            boolean hasResults = ps.execute();
+
+
+            //
+            // callable statement processing
+            //
+            if (sqlStatement.isCallableStatement()) {
+                SQLParameter[] params = (SQLParameter[]) args[0];
+                for (int i = 0; i < params.length; i++) {
+                    if (params[i].dir != SQLParameter.IN) {
+                        params[i].value = ((CallableStatement) ps).getObject(i + 1);
+                    }
+                }
+                return null;
+            }
+
+
+            //
+            // process returned data
+            //
+            ResultSet rs = null;
+            int updateCount = ps.getUpdateCount();
+
+            if (hasResults) {
+                rs = ps.getResultSet();
+            }
+
+            if (sqlStatement.getsGeneratedKeys()) {
+                rs = ps.getGeneratedKeys();
+                hasResults = true;
+            }
+
+            if (!hasResults && updateCount > -1) {
+                boolean moreResults = ps.getMoreResults();
+                int tempUpdateCount = ps.getUpdateCount();
+
+                while ((moreResults && rs == null) || tempUpdateCount > -1) {
+                    if (moreResults) {
+                        rs = ps.getResultSet();
+                        hasResults = true;
+                        moreResults = false;
+                        tempUpdateCount = -1;
+                    } else {
+                        moreResults = ps.getMoreResults();
+                        tempUpdateCount = ps.getUpdateCount();
+                    }
+                }
+            }
+
+            Object returnObject = null;
+            if (hasResults) {
+
+                //
+                // if a result set mapper was specified in the methods annotation, use it
+                // otherwise find the mapper for the return type in the hashmap
+                //
+                final Class resultSetMapperClass = methodSQL.resultSetMapper();
+                final ResultSetMapper rsm;
+                if (!UndefinedResultSetMapper.class.isAssignableFrom(resultSetMapperClass)) {
+                    if (ResultSetMapper.class.isAssignableFrom(resultSetMapperClass)) {
+                        rsm = (ResultSetMapper) resultSetMapperClass.newInstance();
+                    } else {
+                        throw new ControlException("Result set mappers must be subclasses of ResultSetMapper.class!");
+                    }
+                } else {
+                    if (_resultMappers.containsKey(returnType)) {
+                        rsm = _resultMappers.get(returnType);
+                    } else {
+                        if (_xmlObjectClass != null && _xmlObjectClass.isAssignableFrom(returnType)) {
+                            rsm = _resultMappers.get(_xmlObjectClass);
+                        } else {
+                            rsm = DEFAULT_MAPPER;
+                        }
+                    }
+                }
+
+                returnObject = rsm.mapToResultType(_context, method, rs, _cal);
+                if (rsm.canCloseResultSet() == false) {
+                    _resources.add(ps);
+                }
+
+                //
+                // empty ResultSet
+                //
+            } else {
+                if (returnType.equals(Void.TYPE)) {
+                    returnObject = null;
+                } else if (returnType.equals(Integer.TYPE)) {
+                    returnObject = new Integer(updateCount);
+                } else if (!sqlStatement.isCallableStatement()) {
+                    throw new ControlException("Method " + method.getName() + "is DML but does not return void or int");
+                }
+            }
+            return returnObject;
+
+        } finally {
+            // Keep statements open that have in-use result sets
+            if (ps != null && !_resources.contains(ps)) {
+                ps.close();
+            }
+        }
+    }
+
+// /////////////////////////////////////////// Private Methods ////////////////////////////////////////////
+
+    /**
+     * Get a connection from a DataSource.
+     *
+     * @param jndiName    Specifed in the subclasse's ConnectionDataSource annotation
+     * @param jndiFactory Specified in the subclasse's ConnectionDataSource Annotation.
+     * @return null if a connection cannot be established
+     * @throws SQLException
+     */
+    private Connection getConnectionFromDataSource(String jndiName,
+                                                   Class<? extends JdbcControl.JndiContextFactory> jndiFactory)
+            throws SQLException
+    {
+
+        Connection con = null;
+        try {
+            JndiContextFactory jf = (JndiContextFactory) jndiFactory.newInstance();
+            Context jndiContext = jf.getContext();
+            _dataSource = (DataSource) jndiContext.lookup(jndiName);
+            con = _dataSource.getConnection();
+        } catch (IllegalAccessException iae) {
+            throw new ControlException("IllegalAccessException:", iae);
+        } catch (InstantiationException ie) {
+            throw new ControlException("InstantiationException:", ie);
+        } catch (NamingException ne) {
+            throw new ControlException("NamingException:", ne);
+        }
+        return con;
+    }
+
+    /**
+     * Get a JDBC connection from the DriverManager.
+     *
+     * @param dbDriverClassName Specified in the subclasse's ConnectionDriver annotation.
+     * @param dbUrlStr          Specified in the subclasse's ConnectionDriver annotation.
+     * @param userName          Specified in the subclasse's ConnectionDriver annotation.
+     * @param password          Specified in the subclasse's ConnectionDriver annotation.
+     * @return null if a connection cannot be established.
+     * @throws SQLException
+     */
+    private Connection getConnectionFromDriverManager(String dbDriverClassName, String dbUrlStr,
+                                                      String userName, String password, String propertiesString)
+            throws SQLException
+    {
+
+        Connection con = null;
+        try {
+            Class.forName(dbDriverClassName);
+            if (!EMPTY_STRING.equals(userName)) {
+                con = DriverManager.getConnection(dbUrlStr, userName, password);
+            } else if (!EMPTY_STRING.equals(propertiesString)) {
+                Properties props = parseProperties(propertiesString);
+                if (props == null) {
+                    throw new ControlException("Invalid properties annotation value: " + propertiesString);
+                }
+                con = DriverManager.getConnection(dbUrlStr, props);
+            } else {
+                con = DriverManager.getConnection(dbUrlStr);
+            }
+        } catch (ClassNotFoundException e) {
+            throw new ControlException("Database driver class not found!", e);
+        }
+        return con;
+    }
+
+    /**
+     * Parse the propertiesString into a Properties object.  The string must have the format of:
+     * propertyName=propertyValue;propertyName=propertyValue;...
+     *
+     * @param propertiesString
+     * @return A Properties instance or null if parse fails
+     */
+    private Properties parseProperties(String propertiesString) {
+        Properties properties = null;
+        String[] propPairs = propertiesString.split(";");
+        if (propPairs.length > 0) {
+            properties = new Properties();
+            for (String propPair : propPairs) {
+                int eq = propPair.indexOf('=');
+                assert eq > -1 : "Invalid properties syntax: " + propertiesString;
+                properties.put(propPair.substring(0, eq), propPair.substring(eq + 1, propPair.length()));
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * Set any custom type mappers specifed in the annotation for the connection.  Used for mapping SQL UDTs to
+     * java classes.
+     *
+     * @param typeMappers An array of TypeMapper.
+     */
+    private void setTypeMappers(TypeMapper[] typeMappers) throws SQLException {
+
+        if (typeMappers.length > 0) {
+            Map<String, Class<?>> mappers = _connection.getTypeMap();
+            for (TypeMapper t : typeMappers) {
+                mappers.put(t.UDTName(), t.mapperClass());
+            }
+            _connection.setTypeMap(mappers);
+        }
+    }
+}