/* * 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.synapse.mediators.db; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.datasources.PerUserPoolDataSource; import org.apache.commons.logging.LogFactory; import org.apache.synapse.ManagedLifecycle; import org.apache.synapse.MessageContext; import org.apache.synapse.SynapseException; import org.apache.synapse.SynapseLog; import org.apache.synapse.commons.datasource.*; import org.apache.synapse.commons.datasource.factory.DataSourceFactory; import org.apache.synapse.commons.jmx.MBeanRepository; import org.wso2.securevault.secret.SecretManager; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.mediators.AbstractMediator; import javax.naming.Context; import javax.sql.DataSource; import javax.xml.namespace.QName; import java.math.BigDecimal; import java.sql.*; import java.sql.Date; import java.util.*; /** * This abstract DB mediator will perform common DB connection pooling etc. for all DB mediators */ public abstract class AbstractDBMediator extends AbstractMediator implements ManagedLifecycle { /** * The information needed to create a data source */ private DataSourceInformation dataSourceInformation; /** * The name of the data source to lookup. */ private String dataSourceName; /** * The information needed to lookup a data source from a JNDI provider */ private Properties jndiProperties = new Properties(); /** * The DataSource to get DB connections */ private DataSource dataSource; /** * MBean for DBPool monitoring */ private DBPoolView dbPoolView; /** * Statements */ private final List<Statement> statementList = new ArrayList<Statement>(); /** * Map to store the pool configuration for de-serialization */ private Map<Object, String> dataSourceProps = new HashMap<Object, String>(); /** * Initializes the mediator - either an existing data source will be looked up * from an in- or external JNDI provider or a custom data source will be created * based on the provide configuration (using Apache DBCP). * * @param se the Synapse environment reference */ public void init(SynapseEnvironment se) { // check whether we shall try to lookup an existing data source or create a new custom data source if (dataSourceName != null) { dataSource = lookupDataSource(dataSourceName, jndiProperties); } else if (dataSourceInformation != null) { dataSource = createCustomDataSource(dataSourceInformation); } } /** * Destroys the mediator. If we are using our custom DataSource, then shut down the connections */ public void destroy() { /* If the DB mediators are used with globally defined data sources, the associated data source is not closed. */ if (dataSourceName != null) { return; } if (this.dataSource instanceof BasicDataSource) { try { ((BasicDataSource) this.dataSource).close(); log.info("Successfully shut down DB connection pool for URL : " + getDSName()); } catch (SQLException e) { log.warn("Error shutting down DB connection pool for URL : " + getDSName()); } } else if (this.dataSource instanceof PerUserPoolDataSource) { ((PerUserPoolDataSource) this.dataSource).close(); log.info("Successfully shut down DB connection pool for URL : " + getDSName()); } } /** * Process each SQL statement against the current message * * @param synCtx the current message * @return true, always */ public boolean mediate(MessageContext synCtx) { if (synCtx.getEnvironment().isDebuggerEnabled()) { if (super.divertMediationRoute(synCtx)) { return true; } } String name = (this instanceof DBLookupMediator ? "DBLookup" : "DBReport"); SynapseLog synLog = getLog(synCtx); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Start : " + name + " mediator"); if (synLog.isTraceTraceEnabled()) { synLog.traceTrace("Message : " + synCtx.getEnvelope()); } } for (Statement aStatement : statementList) { if (aStatement != null) { processStatement(aStatement, synCtx); } } if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("End : " + name + " mediator"); } return true; } /** * Subclasses must specify how each SQL statement is processed * * @param query the SQL statement * @param msgCtx current message */ abstract protected void processStatement(Statement query, MessageContext msgCtx); /** * Return the name or (hopefully) unique connection URL specific to the DataSource being used * This is used for logging purposes only * * @return a unique name or URL to refer to the DataSource being used */ protected String getDSName() { if (dataSourceName != null) { return dataSourceName; } else if (dataSourceInformation != null) { String name = dataSourceInformation.getUrl(); if (name == null) { name = dataSourceInformation.getDatasourceName(); } return name; } return null; } public void setDataSourceInformation(DataSourceInformation dataSourceInformation) { this.dataSourceInformation = dataSourceInformation; } public void setJndiProperties(Properties jndiProperties) { this.jndiProperties = jndiProperties; } public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void setDataSourceName(String dataSourceName) { this.dataSourceName = dataSourceName; } public void addDataSourceProperty(QName name, String value) { dataSourceProps.put(name, value); } public void addDataSourceProperty(String name, String value) { dataSourceProps.put(name, value); } public void addStatement(Statement stmnt) { statementList.add(stmnt); } public List<Statement> getStatementList() { return statementList; } public DBPoolView getDbPoolView() { return dbPoolView; } public void setDbPoolView(DBPoolView dbPoolView) { this.dbPoolView = dbPoolView; } /** * Return a Prepared statement for the given Statement object, which is ready to be executed * * @param stmnt SQL stataement to be executed * @param con The connection to be used * @param msgCtx Current message context * @return a PreparedStatement * @throws SQLException on error */ protected PreparedStatement getPreparedStatement(Statement stmnt, Connection con, MessageContext msgCtx) throws SQLException { SynapseLog synLog = getLog(msgCtx); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Getting a connection from DataSource " + getDSName() + " and preparing statement : " + stmnt.getRawStatement()); } if (con == null) { String msg = "Connection from DataSource " + getDSName() + " is null."; log.error(msg); throw new SynapseException(msg); } if (dataSource instanceof BasicDataSource) { BasicDataSource basicDataSource = (BasicDataSource) dataSource; int numActive = basicDataSource.getNumActive(); int numIdle = basicDataSource.getNumIdle(); String connectionId = Integer.toHexString(con.hashCode()); DBPoolView dbPoolView = getDbPoolView(); if (dbPoolView != null) { dbPoolView.setNumActive(numActive); dbPoolView.setNumIdle(numIdle); dbPoolView.updateConnectionUsage(connectionId); } if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("[ DB Connection : " + con + " ]"); synLog.traceOrDebug("[ DB Connection instance identifier : " + connectionId + " ]"); synLog.traceOrDebug("[ Number of Active Connection : " + numActive + " ]"); synLog.traceOrDebug("[ Number of Idle Connection : " + numIdle + " ]"); } } PreparedStatement ps = con.prepareStatement(stmnt.getRawStatement()); // set parameters if any List<Statement.Parameter> params = stmnt.getParameters(); int column = 1; for (Statement.Parameter param : params) { if (param == null) { continue; } String value = (param.getPropertyName() != null ? param.getPropertyName() : param.getXpath().stringValueOf(msgCtx)); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Setting as parameter : " + column + " value : " + value + " as JDBC Type : " + param.getType() + "(see java.sql.Types for valid " + "types)"); } switch (param.getType()) { // according to J2SE 1.5 /docs/guide/jdbc/getstart/mapping.html case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: { if (value != null && value.length() != 0) { ps.setString(column++, value); } else { ps.setString(column++, null); } break; } case Types.NUMERIC: case Types.DECIMAL: { if (value != null && value.length() != 0) { ps.setBigDecimal(column++, new BigDecimal(value)); } else { ps.setBigDecimal(column++, null); } break; } case Types.BIT: { if (value != null && value.length() != 0) { ps.setBoolean(column++, Boolean.parseBoolean(value)); } else { ps.setNull(column++, Types.BIT); } break; } case Types.TINYINT: { if (value != null && value.length() != 0) { ps.setByte(column++, Byte.parseByte(value)); } else { ps.setNull(column++, Types.TINYINT); } break; } case Types.SMALLINT: { if (value != null && value.length() != 0) { ps.setShort(column++, Short.parseShort(value)); } else { ps.setNull(column++, Types.SMALLINT); } break; } case Types.INTEGER: { if (value != null && value.length() != 0) { ps.setInt(column++, Integer.parseInt(value)); } else { ps.setNull(column++, Types.INTEGER); } break; } case Types.BIGINT: { if (value != null && value.length() != 0) { ps.setLong(column++, Long.parseLong(value)); } else { ps.setNull(column++, Types.BIGINT); } break; } case Types.REAL: { if (value != null && value.length() != 0) { ps.setFloat(column++, Float.parseFloat(value)); } else { ps.setNull(column++, Types.REAL); } break; } case Types.FLOAT: { if (value != null && value.length() != 0) { ps.setDouble(column++, Double.parseDouble(value)); } else { ps.setNull(column++, Types.FLOAT); } break; } case Types.DOUBLE: { if (value != null && value.length() != 0) { ps.setDouble(column++, Double.parseDouble(value)); } else { ps.setNull(column++, Types.DOUBLE); } break; } // skip BINARY, VARBINARY and LONGVARBINARY case Types.DATE: { if (value != null && value.length() != 0) { ps.setDate(column++, Date.valueOf(value)); } else { ps.setNull(column++, Types.DATE); } break; } case Types.TIME: { if (value != null && value.length() != 0) { ps.setTime(column++, Time.valueOf(value)); } else { ps.setNull(column++, Types.TIME); } break; } case Types.TIMESTAMP: { if (value != null && value.length() != 0) { ps.setTimestamp(column++, Timestamp.valueOf(value)); } else { ps.setNull(column++, Types.TIMESTAMP); } break; } // skip CLOB, BLOB, ARRAY, DISTINCT, STRUCT, REF, JAVA_OBJECT default: { String msg = "Trying to set an un-supported JDBC Type : " + param.getType() + " against column : " + column + " and statement : " + stmnt.getRawStatement() + " used by a DB mediator against DataSource : " + getDSName() + " (see java.sql.Types for valid type values)"; handleException(msg, msgCtx); } } } if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Successfully prepared statement : " + stmnt.getRawStatement() + " against DataSource : " + getDSName()); } return ps; } /** * Lookup the DataSource on JNDI using the specified name and optional properties * * @param dataSourceName the name of the data source to lookup * @param jndiProperties the JNDI properties identifying a data source provider * @return a DataSource looked up using the specified JNDI properties */ private DataSource lookupDataSource(String dataSourceName, Properties jndiProperties) { DataSource dataSource = null; RepositoryBasedDataSourceFinder finder = DataSourceRepositoryHolder.getInstance() .getRepositoryBasedDataSourceFinder(); if (finder.isInitialized()) { // first try a lookup based on the data source name only dataSource = finder.find(dataSourceName); } if (dataSource == null) { // decrypt the password if needed String password = jndiProperties.getProperty(Context.SECURITY_CREDENTIALS); if (password != null && !"".equals(password)) { jndiProperties.put(Context.SECURITY_CREDENTIALS, getActualPassword(password)); } // lookup the data source using the specified jndi properties dataSource = DataSourceFinder.find(dataSourceName, jndiProperties); if (dataSource == null) { handleException("Cannot find a DataSource " + dataSourceName + " for given JNDI" + " properties :" + jndiProperties); } } MBeanRepository mBeanRepository = DatasourceMBeanRepository.getInstance(); Object mBean = mBeanRepository.getMBean(dataSourceName); if (mBean instanceof DBPoolView) { setDbPoolView((DBPoolView) mBean); } log.info("Successfully looked up datasource " + dataSourceName + "."); return dataSource; } /** * Create a custom DataSource using the specified data source information. * * @param dataSourceInformation the data source information to create a data source * @return a DataSource created using specified properties */ protected DataSource createCustomDataSource(DataSourceInformation dataSourceInformation) { DataSource dataSource = DataSourceFactory.createDataSource(dataSourceInformation); if (dataSource != null) { log.info("Successfully created data source for " + dataSourceInformation.getUrl() + "."); } return dataSource; } /** * Get the password from SecretManager . here only use SecretManager * * @param aliasPasword alias password * @return if the SecretManager is initiated , then , get the corresponding secret * , else return alias itself */ private String getActualPassword(String aliasPasword) { SecretManager secretManager = SecretManager.getInstance(); if (secretManager.isInitialized()) { return secretManager.getSecret(aliasPasword); } return aliasPasword; } protected void handleException(String message) { LogFactory.getLog(this.getClass()).error(message); throw new SynapseException(message); } public Map<Object, String> getDataSourceProps() { return dataSourceProps; } }