/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* 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 the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.jpa.BasePortalJpaDao;
import org.apereo.portal.utils.MovingAverage;
import org.apereo.portal.utils.MovingAverageSample;
import org.apereo.portal.utils.PortalApplicationContextLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
/**
* Provides relational database access and helper methods. A static routine determines if the
* database/driver supports prepared statements and/or outer joins.
*
* <p>This class provides database access as a service. Via the class, uPortal code can obtain a
* connection to the core uPortal database as well as to other databases available via JNDI. (Doing
* that JNDI lookup directly allows your code to avoid dependence upon this class.) This class
* provides traditional getConnection() methods as well as static covers for getting a reference to
* the backing DataSource.
*
* <p>This class also provides helper methods for manipulating connections. Mostof the methods are
* wrappers around methods on the underlying Connection that handle (log and swallow) the
* SQLExceptions that the underlying methods declare to be thrown (these helpers also catch and log
* RuntimeExceptions encountered). They provide an alternative to trying and catching those methods
* using the JDBC APIs directly.
*
*/
public class RDBMServices {
/** Name of the default portal database expected in the Spring application context */
public static final String PORTAL_DB = BasePortalJpaDao.PERSISTENCE_UNIT_NAME;
/**
* Name of the {@link org.apereo.portal.jdbc.IDatabaseMetadata} expected in the Spring application
* context
*/
public static final String PORTAL_DB_METADATA = "PortalDB.metadata";
private static final Log LOG = LogFactory.getLog(RDBMServices.class);
//DBFlag constants
private static final String FLAG_TRUE = "Y";
private static final String FLAG_TRUE_OTHER = "T";
private static final String FLAG_FALSE = "N";
// Metric counters
private static final MovingAverage databaseTimes = new MovingAverage();
private static MovingAverageSample lastDatabase = new MovingAverageSample();
private static AtomicInteger activeConnections = new AtomicInteger();
private static int maxConnections = 0;
/**
* Gets the default DataSource. If no server is found a runtime exception will be thrown. This
* method will never return null.
*
* @return the core uPortal DataSource
* @throws RuntimeException on failure
* @deprecated Where possible code should be injected with a {@link DataSource} object via the
* Spring application context
*/
@Deprecated
public static DataSource getDataSource() {
return getDataSource(PORTAL_DB);
}
/**
* Gets a named DataSource from JNDI, with special handling for the PORTAL_DB datasource.
* Successful lookups are cached and not done again. Lookup failure is remembered and blocks
* retry for a number of milliseconds specified by {@link #JNDI_RETRY_TIME} to reduce JNDI
* overhead and log spam.
*
* <p>There are two ways in which we handle the core uPortal DataSource specially.
*
* <p>We determine and remember metadata in an DbMetaData object for the core uPortal
* DataSource. We do not compute this DbMetaData for any other DataSource.
*
* <p>We fall back on using rdbm.properties to construct our core uPortal DataSource in the case
* where we cannot find it from JNDI. If the portal property
* org.apereo.portal.jdbc.RDBMServices.getDatasourceFromJNDI is true, we first first try to get
* the connection by looking in the JNDI context for the name defined by the portal property
* org.apereo.portal.jdbc.RDBMServices.PortalDatasourceJndiName .
*
* <p>If we were not configured to check JNDI or we didn't find it in JNDI having checked, we
* then fall back on rdbm.properties.
*
* @param name The name of the DataSource to get.
* @return A named DataSource or <code>null</code> if one cannot be found.
* @deprecated Where possible code should be injected with a {@link DataSource} object via the
* Spring application context
*/
@Deprecated
public static DataSource getDataSource(String name) {
if (PORTAL_DB.equals(name)) {
return PortalDbLocator.getPortalDb();
}
final ApplicationContext applicationContext =
PortalApplicationContextLocator.getApplicationContext();
final DataSource dataSource =
(DataSource) applicationContext.getBean(name, DataSource.class);
return dataSource;
}
/** @return Return the current number of active connections */
public static int getActiveConnectionCount() {
return activeConnections.intValue();
}
/** @return Return the maximum number of connections */
public static int getMaxConnectionCount() {
return maxConnections;
}
public static MovingAverageSample getLastDatabase() {
return lastDatabase;
}
/**
* Gets a database connection to the portal database. If datasource not available a runtime
* exception is thrown
*
* @return a database Connection object
* @throws DataAccessException if unable to return a connection
* @deprecated Where possible code should be injected with a {@link DataSource} object via the
* Spring application context
*/
@Deprecated
public static Connection getConnection() {
return getConnection(PORTAL_DB);
}
/**
* Returns a connection produced by a DataSource found in the JNDI context. The DataSource
* should be configured and loaded into JNDI by the J2EE container or may be the portal default
* database.
*
* @param dbName the database name which will be retrieved from the JNDI context relative to
* "jdbc/"
* @return a database Connection object or <code>null</code> if no Connection
* @deprecated Where possible code should be injected with a {@link DataSource} object via the
* Spring application context
*/
@Deprecated
public static Connection getConnection(String dbName) {
final DataSource dataSource = getDataSource(dbName);
try {
final long start = System.currentTimeMillis();
final Connection c = dataSource.getConnection();
lastDatabase = databaseTimes.add(System.currentTimeMillis() - start); // metric
final int current = activeConnections.incrementAndGet();
if (current > maxConnections) {
maxConnections = current;
}
return c;
} catch (SQLException e) {
throw new DataAccessResourceFailureException(
"RDBMServices sql error trying to get connection to " + dbName, e);
}
}
/**
* Releases database connection. Unlike the underlying connection.close(), this method does not
* throw SQLException or any other exception. It will fail silently from the perspective of
* calling code, logging errors using Commons Logging.
*
* @param con a database Connection object
* @deprecated Where possible code should be injected with a {@link DataSource} object via the
* Spring application context
*/
@Deprecated
public static void releaseConnection(final Connection con) {
// If we had failed allocating the connection, insure we don't try to count it or close it. UP-4446
if (con != null) {
try {
activeConnections.decrementAndGet();
con.close();
} catch (Exception e) {
if (LOG.isWarnEnabled()) LOG.warn("Error closing Connection: " + con, e);
}
}
}
//******************************************
// Utility Methods
//******************************************
/**
* Close a ResultSet
*
* @param rs a database ResultSet object
*/
public static void closeResultSet(final ResultSet rs) {
if (rs == null) {
return;
}
try {
rs.close();
} catch (Exception e) {
if (LOG.isWarnEnabled()) LOG.warn("Error closing ResultSet: " + rs, e);
}
}
/**
* Close a Statement
*
* @param st a database Statement object
*/
public static void closeStatement(final Statement st) {
if (st == null) {
return;
}
try {
st.close();
} catch (Exception e) {
if (LOG.isWarnEnabled()) LOG.warn("Error closing Statement: " + st, e);
}
}
/**
* Commit pending transactions. Unlike the underlying commit(), this method does not throw
* SQLException or any other exception. It will fail silently from the perspective of calling
* code, logging any errors using Commons Logging.
*
* @param connection
*/
public static final void commit(final Connection connection) {
if (connection == null) {
return;
}
try {
connection.commit();
} catch (Exception e) {
if (LOG.isWarnEnabled()) LOG.warn("Error committing Connection: " + connection, e);
}
}
/**
* Set auto commit state for the connection. Unlike the underlying connection.setAutoCommit(),
* this method does not throw SQLException or any other Exception. It fails silently from the
* perspective of calling code, logging any errors encountered using Commons Logging.
*
* @param connection
* @param autocommit
*/
public static final void setAutoCommit(final Connection connection, boolean autocommit) {
try {
connection.setAutoCommit(autocommit);
} catch (Exception e) {
if (LOG.isWarnEnabled())
LOG.warn("Error committing Connection: " + connection + " to: " + autocommit, e);
}
}
/**
* rollback unwanted changes to the database
*
* @param connection
*/
public static final void rollback(final Connection connection) {
try {
connection.rollback();
} catch (Exception e) {
if (LOG.isWarnEnabled()) LOG.warn("Error rolling back Connection: " + connection, e);
}
}
/**
* Returns the name of the JDBC driver being used for the default uPortal database connections.
*
* @return the name of the JDBC Driver.
*/
public static String getJdbcDriver() {
final IDatabaseMetadata dbMetaData = getDbMetaData();
return dbMetaData.getJdbcDriver();
}
/** Gets the JDBC URL of the default uPortal database connections. */
public static String getJdbcUrl() {
final IDatabaseMetadata dbMetaData = getDbMetaData();
return dbMetaData.getJdbcUrl();
}
/**
* Get the username under which we are connecting for the default uPortal database connections.
*/
public static String getJdbcUser() {
final IDatabaseMetadata dbMetaData = getDbMetaData();
return dbMetaData.getJdbcUser();
}
//******************************************
// Data Type / Formatting Methods
//******************************************
/**
* Return DB format of a boolean. "Y" for true and "N" for false.
*
* @param flag true or false
* @return either "Y" or "N"
*/
public static final String dbFlag(final boolean flag) {
if (flag) {
return FLAG_TRUE;
}
return FLAG_FALSE;
}
/**
* Return boolean value of DB flag, "Y" or "N".
*
* @param flag either "Y" or "N"
* @return boolean true or false
*/
public static final boolean dbFlag(final String flag) {
return flag != null
&& (FLAG_TRUE.equalsIgnoreCase(flag) || FLAG_TRUE_OTHER.equalsIgnoreCase(flag));
}
/**
* Make a string SQL safe
*
* @param sql
* @return SQL safe string
*/
public static final String sqlEscape(final String sql) {
if (sql == null) {
return "";
}
int primePos = sql.indexOf("'");
if (primePos == -1) {
return sql;
}
final StringBuffer sb = new StringBuffer(sql.length() + 4);
int startPos = 0;
do {
sb.append(sql.substring(startPos, primePos + 1));
sb.append("'");
startPos = primePos + 1;
primePos = sql.indexOf("'", startPos);
} while (primePos != -1);
sb.append(sql.substring(startPos));
return sb.toString();
}
/**
* Get metadata about the default DataSource.
*
* @return metadata about the default DataSource.
* @deprecated Use the bean named PortalDB.metadata instead
*/
@Deprecated
public static IDatabaseMetadata getDbMetaData() {
return PortalDbMetadataLocator.getPortalDbMetadata();
}
}