| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.openjpa.jdbc.schema; |
| |
| import java.sql.Connection; |
| import java.sql.SQLException; |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.Properties; |
| |
| import javax.sql.DataSource; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.lib.conf.Configurable; |
| import org.apache.openjpa.lib.conf.Configuration; |
| import org.apache.openjpa.lib.conf.Configurations; |
| import org.apache.openjpa.lib.util.Closeable; |
| |
| /** |
| * Commons DBCP basic pooling driver data source. |
| * The commons-dbcp packages must be on the class path for this plugin to work, |
| * as it WILL NOT fall back to non-DBCP mode if they are missing. For automatic |
| * usage of Commons DBCP when available, use AutoDriverDataSource instead. |
| */ |
| public class DBCPDriverDataSource |
| extends SimpleDriverDataSource implements Configurable, Closeable { |
| |
| private static String DBCPPACKAGENAME = "org.apache.commons.dbcp2"; |
| private static String DBCPBASICDATASOURCENAME = "org.apache.commons.dbcp2.BasicDataSource"; |
| private static Class<?> _dbcpClass; |
| private static Boolean _dbcpAvail; |
| private static RuntimeException _dbcpEx; |
| |
| protected JDBCConfiguration conf; |
| private DataSource _ds; |
| |
| @Override |
| public Connection getConnection(Properties props) throws SQLException { |
| return getDBCPConnection(props); |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| try { |
| if (_ds != null) { |
| if (isDBCPLoaded(getClassLoader())) { |
| ((org.apache.commons.dbcp2.BasicDataSource)_dbcpClass.cast(_ds)).close(); |
| } |
| } |
| } catch (Exception e) { |
| // no-op |
| } catch (Throwable t) { |
| // no-op |
| } finally { |
| _ds = null; |
| } |
| } |
| |
| protected Connection getDBCPConnection(Properties props) throws SQLException { |
| Connection con = getDBCPDataSource(props).getConnection(); |
| if (con == null) { |
| throw new SQLException(_eloc.get("dbcp-ds-null", |
| DBCPBASICDATASOURCENAME, getConnectionDriverName(), getConnectionURL()).getMessage()); |
| } |
| return con; |
| } |
| |
| protected DataSource getDBCPDataSource(Properties props) { |
| if (isDBCPLoaded(getClassLoader())) { |
| if (_ds == null) { |
| try { |
| Properties dbcpProps = updateDBCPProperties(props); |
| _ds = (DataSource) Configurations.newInstance(DBCPBASICDATASOURCENAME, conf, |
| dbcpProps, getClassLoader()); |
| } catch (Exception e) { |
| _dbcpEx = new RuntimeException(_eloc.get("driver-null", DBCPBASICDATASOURCENAME).getMessage(), e); |
| } |
| return _ds; |
| } else { |
| return _ds; |
| } |
| } else { |
| // user choose DBCP, so fail if it isn't on the classpath |
| if (_dbcpEx == null) |
| _dbcpEx = new RuntimeException(_eloc.get("driver-null", DBCPBASICDATASOURCENAME).getMessage()); |
| throw _dbcpEx; |
| } |
| } |
| |
| /** |
| * This method should not throw an exception, as it is called by |
| * AutoDriverDataSource to determine if user already specified |
| * to use Commons DBCP. |
| * @return true if ConnectionDriverName contains org.apache.commons.dbcp2, |
| * otherwise false |
| */ |
| protected boolean isDBCPDataSource() { |
| return (getConnectionDriverName() != null && |
| getConnectionDriverName().toLowerCase(Locale.ENGLISH).indexOf(DBCPPACKAGENAME) >= 0); |
| } |
| |
| /** |
| * This method should not throw an exception, as it is called by |
| * AutoDriverDataSource to determine if it should use DBCP or not |
| * based on if org.apache.commons.dbcp2.BasicDataSource can be loaded. |
| * @return true if Commons DBCP was found on the classpath, otherwise false |
| */ |
| static protected boolean isDBCPLoaded(ClassLoader cl) { |
| if (Boolean.TRUE.equals(_dbcpAvail) && (_dbcpClass != null)) { |
| return true; |
| } else if (Boolean.FALSE.equals(_dbcpAvail)) { |
| return false; |
| } else { |
| // first time checking, so try to load it |
| try { |
| _dbcpClass = Class.forName(DBCPBASICDATASOURCENAME, true, cl); |
| _dbcpAvail = Boolean.TRUE; |
| return true; |
| } catch (Exception e) { |
| _dbcpAvail = Boolean.FALSE; |
| // save exception details for later instead of throwing here |
| _dbcpEx = new RuntimeException(_eloc.get("driver-null", DBCPBASICDATASOURCENAME).getMessage(), e); |
| } |
| return _dbcpAvail.booleanValue(); |
| } |
| } |
| |
| /** |
| * Normalize properties for Commons DBCP. This should be done for every call from DataSourceFactory, |
| * as we do not have a pre-configured Driver to reuse. |
| * @param props |
| * @return updated properties |
| */ |
| private Properties updateDBCPProperties(Properties props) { |
| Properties dbcpProps = mergeConnectionProperties(props); |
| |
| // only perform the following check for the first connection attempt (_driverClassName == null), |
| // as multiple connections could be requested (like from SchemaTool) and isDBCPDriver() will be true |
| if (isDBCPDataSource()) { |
| String propDriver = hasProperty(dbcpProps, "driverClassName"); |
| if (propDriver == null || propDriver.trim().isEmpty()) { |
| // if user specified DBCP for the connectionDriverName, then make sure they supplied a DriverClassName |
| throw new RuntimeException(_eloc.get("connection-property-invalid", "DriverClassName", |
| propDriver).getMessage()); |
| } |
| propDriver = hasProperty(dbcpProps, "url"); |
| if (propDriver == null || propDriver.trim().isEmpty()) { |
| // if user specified DBCP for the connectionDriverName, then make sure they supplied a Url |
| throw new RuntimeException(_eloc.get("connection-property-invalid", "URL", |
| propDriver).getMessage()); |
| } |
| } else { |
| // set Commons DBCP expected DriverClassName to the original connection driver name |
| dbcpProps.setProperty(hasKey(dbcpProps, "driverClassName", "driverClassName"), getConnectionDriverName()); |
| // set Commons DBCP expected URL property |
| dbcpProps.setProperty(hasKey(dbcpProps, "url", "url"), getConnectionURL()); |
| } |
| |
| // Commons DBCP requires non-Null username/password values in the connection properties |
| if (hasKey(dbcpProps, "username") == null) { |
| if (getConnectionUserName() != null) |
| dbcpProps.setProperty("username", getConnectionUserName()); |
| else |
| dbcpProps.setProperty("username", ""); |
| } |
| // Commons DBCP requires non-Null username/password values in the connection properties |
| if (hasKey(dbcpProps, "password") == null) { |
| if (getConnectionPassword() != null) |
| dbcpProps.setProperty("password", getConnectionPassword()); |
| else |
| dbcpProps.setProperty("password", ""); |
| } |
| |
| // set some default properties for DBCP |
| if (hasKey(dbcpProps, "maxActive") == null) { |
| dbcpProps.setProperty("maxActive", "10"); |
| } |
| if (hasKey(dbcpProps, "maxIdle") == null) { |
| // by default we set maxIdle to the same value as maxActive. |
| dbcpProps.setProperty("maxIdle", dbcpProps.getProperty("maxActive")); |
| } |
| if (hasKey(dbcpProps, "minIdle") == null) { |
| dbcpProps.setProperty("minIdle", "0"); |
| } |
| |
| return dbcpProps; |
| } |
| |
| /** |
| * Merge the passed in properties with a copy of the existing _connectionProperties |
| * @param props |
| * @return Merged properties |
| */ |
| private Properties mergeConnectionProperties(final Properties props) { |
| Properties mergedProps = new Properties(); |
| mergedProps.putAll(getConnectionProperties()); |
| |
| // need to map "user" to "username" for Commons DBCP |
| String uid = removeProperty(mergedProps, "user"); |
| if (uid != null) { |
| mergedProps.setProperty("username", uid); |
| } |
| |
| // now, merge in any passed in properties |
| if (props != null && !props.isEmpty()) { |
| for (Iterator<Object> itr = props.keySet().iterator(); itr.hasNext();) { |
| String key = (String)itr.next(); |
| String value = props.getProperty(key); |
| // need to map "user" to "username" for Commons DBCP |
| if ("user".equalsIgnoreCase(key)) { |
| key = "username"; |
| } |
| // case-insensitive search for existing key |
| String existingKey = hasKey(mergedProps, key); |
| if (existingKey != null) { |
| // update existing entry |
| mergedProps.setProperty(existingKey, value); |
| } else { |
| // add property to the merged set |
| mergedProps.setProperty(key, value); |
| } |
| } |
| } |
| return mergedProps; |
| } |
| |
| /** |
| * Case-insensitive search of the given properties for the given key. |
| * @param props |
| * @param key |
| * @return Key name as found in properties or null if it was not found. |
| */ |
| private String hasKey(Properties props, String key) { |
| return hasKey(props, key, null); |
| } |
| |
| /** |
| * Case-insensitive search of the given properties for the given key. |
| * @param props |
| * @param key |
| * @param defaultKey |
| * @return Key name as found in properties or the given defaultKey if it was not found. |
| */ |
| private String hasKey(Properties props, String key, String defaultKey) |
| { |
| if (props != null && key != null) { |
| for (Iterator<Object> itr = props.keySet().iterator(); itr.hasNext();) { |
| String entry = (String)itr.next(); |
| if (key.equalsIgnoreCase(entry)) |
| return entry; |
| } |
| } |
| return defaultKey; |
| } |
| |
| private String hasProperty(Properties props, String key) { |
| if (props != null && key != null) { |
| String entry = hasKey(props, key); |
| if (entry != null) |
| return props.getProperty(entry); |
| } |
| return null; |
| |
| } |
| |
| private String removeProperty(Properties props, String key) { |
| if (props != null && key != null) { |
| String entry = hasKey(props, key); |
| if (entry != null) |
| return (String)props.remove(entry); |
| } |
| return null; |
| } |
| |
| // Configurable interface methods |
| @Override |
| public void setConfiguration(Configuration conf) { |
| if (conf instanceof JDBCConfiguration) |
| this.conf = (JDBCConfiguration)conf; |
| } |
| |
| @Override |
| public void startConfiguration() { |
| // no-op |
| } |
| |
| @Override |
| public void endConfiguration() { |
| // no-op |
| } |
| |
| } |
| |