package com.github.gquintana.metrics.sql; /* * #%L * Metrics SQL * %% * Copyright (C) 2014 Open-Source * %% * 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. * #L% */ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import javax.sql.DataSource; import javax.sql.PooledConnection; import javax.sql.RowSet; import javax.sql.XAConnection; import javax.sql.XADataSource; import javax.sql.rowset.CachedRowSet; import javax.sql.rowset.FilteredRowSet; import javax.sql.rowset.JdbcRowSet; import javax.sql.rowset.JoinRowSet; import javax.sql.rowset.WebRowSet; import com.github.gquintana.metrics.proxy.ProxyFactory; import com.github.gquintana.metrics.proxy.ReflectProxyFactory; /** * Factory of {@code JdbcProxyHandler} sub classes, central class of Metrics SQL. * It can be used to wrap any JDBC component (connection, statement, * result set...). */ public class JdbcProxyFactory { /** * Strategy used to get metric name */ private final MetricNamingStrategy metricNamingStrategy; /** * Proxy factory */ private final ProxyFactory proxyFactory; /** * Constructor using default {@link ReflectProxyFactory} and default {@link DefaultMetricNamingStrategy} */ public JdbcProxyFactory(MetricRegistry metricRegistry) { this(new DefaultMetricNamingStrategy(metricRegistry)); } /** * Constructor * * @param namingStrategy Naming strategy used to get metrics from SQL */ public JdbcProxyFactory(MetricNamingStrategy namingStrategy) { this(namingStrategy, new ReflectProxyFactory()); } /** * Constructor * * @param namingStrategy Naming strategy used to get metrics from SQL * @param proxyFactory AbstractProxyFactory to use for proxy creation */ public JdbcProxyFactory(MetricNamingStrategy namingStrategy, ProxyFactory proxyFactory) { this.metricNamingStrategy = namingStrategy; this.proxyFactory = proxyFactory; } /** * Create a proxy for given JDBC proxy handler * @param <T> Proxy type * @param proxyHandler Proxy handler * @return Proxy */ private <T> T newProxy(JdbcProxyHandler<T> proxyHandler) { return (T) proxyFactory.newProxy(proxyHandler, proxyHandler.getProxyClass()); } /** * Wrap a data source to monitor it. * * @param connectionFactoryName Data source name * @param wrappedDataSource Data source to wrap * @return Wrapped data source */ public DataSource wrapDataSource(String connectionFactoryName, DataSource wrappedDataSource) { return newProxy(new DataSourceProxyHandler(wrappedDataSource, connectionFactoryName, this)); } /** * Wrap an XA data source to monitor it. * * @param connectionFactoryName Data source name * @param wrappedDataSource XA Data source to wrap * @return Wrapped XA data source */ public XADataSource wrapXADataSource(String connectionFactoryName, XADataSource wrappedDataSource) { return newProxy(new XADataSourceProxyHandler(wrappedDataSource, connectionFactoryName, this)); } /** * Wrap a pooled connection to monitor it. * * @param connectionFactoryName Data source/Driver name * @param wrappedConnection Pooled connection to wrap * @return Wrapped pooled connection */ public PooledConnection wrapPooledConnection(String connectionFactoryName, PooledConnection wrappedConnection) { Timer.Context lifeTimerContext = metricNamingStrategy.startPooledConnectionTimer(connectionFactoryName); return newProxy(new PooledConnectionProxyHandler<PooledConnection>(wrappedConnection, PooledConnection.class, connectionFactoryName, this, lifeTimerContext)); } /** * Wrap an XA connection to monitor it. * * @param connectionFactoryName Data source/Driver name * @param wrappedConnection XA connection to wrap * @return XA pooled connection */ public XAConnection wrapXAConnection(String connectionFactoryName, XAConnection wrappedConnection) { Timer.Context lifeTimerContext = metricNamingStrategy.startPooledConnectionTimer(connectionFactoryName); return newProxy(new PooledConnectionProxyHandler<XAConnection>(wrappedConnection, XAConnection.class, connectionFactoryName, this, lifeTimerContext)); } /** * Wrap a connection to monitor it. * * @param connectionFactoryName Data source/Driver name * @param wrappedConnection Connection to wrap * @return Wrapped connection */ public Connection wrapConnection(String connectionFactoryName, Connection wrappedConnection) { Timer.Context lifeTimerContext = metricNamingStrategy.startConnectionTimer(connectionFactoryName); return newProxy(new ConnectionProxyHandler(wrappedConnection, connectionFactoryName, this, lifeTimerContext)); } /** * Wrap a simple statement to monitor it. * * @param connectionFactoryName Data source/Driver name * @param statement Statement to wrap * @return Wrapped statement */ public Statement wrapStatement(String connectionFactoryName, Statement statement) { Timer.Context lifeTimerContext = metricNamingStrategy.startStatementTimer(connectionFactoryName); return newProxy(new StatementProxyHandler(statement, connectionFactoryName, this, lifeTimerContext)); } /** * Start Timer when statement is executed * * @param connectionFactoryName DataSource/Driver name * @param sql SQL query * @return Started timer context or null */ public StatementTimerContext startStatementExecuteTimer(String connectionFactoryName, String sql) { return metricNamingStrategy.startStatementExecuteTimer(connectionFactoryName, sql); } /** * Wrap a prepared statement to monitor it. * * @param connectionFactoryName Data source/Driver name * @param preparedStatement Prepared statement to wrap * @param sql SQL used for creation * @return Wrapped prepared statement */ public PreparedStatement wrapPreparedStatement(String connectionFactoryName, PreparedStatement preparedStatement, String sql) { StatementTimerContext lifeTimerContext = metricNamingStrategy.startPreparedStatementTimer(connectionFactoryName, sql, null); PreparedStatementProxyHandler proxyHandler; if (lifeTimerContext==null) { proxyHandler = new PreparedStatementProxyHandler(preparedStatement, connectionFactoryName, this, null, sql, null); } else { proxyHandler = new PreparedStatementProxyHandler(preparedStatement, connectionFactoryName, this, lifeTimerContext.getTimerContext(), lifeTimerContext.getSql(), lifeTimerContext.getSqlId()); } return newProxy(proxyHandler); } /** * Start timer measuring {@link PreparedStatement#execute() } * @param connectionFactoryName Connection factory name * @param sql SQL query * @param sqlId SQL Id * @return Started timer context or null */ public StatementTimerContext startPreparedStatementExecuteTimer(String connectionFactoryName, String sql, String sqlId) { return metricNamingStrategy.startPreparedStatementExecuteTimer(connectionFactoryName, sql, sqlId); } /** * Wrap a callable statement to monitor it. * * @param connectionFactoryName Data source/Driver name * @param callableStatement Prepared statement to wrap * @param sql SQL used for creation * @return Wrapped prepared statement */ public CallableStatement wrapCallableStatement(String connectionFactoryName, CallableStatement callableStatement, String sql) { StatementTimerContext lifeTimerContext = metricNamingStrategy.startCallableStatementTimer(connectionFactoryName, sql, null); CallableStatementProxyHandler proxyHandler; if (lifeTimerContext==null) { proxyHandler = new CallableStatementProxyHandler(callableStatement, connectionFactoryName, this, null, sql, null); } else { proxyHandler = new CallableStatementProxyHandler(callableStatement, connectionFactoryName, this, lifeTimerContext.getTimerContext(), lifeTimerContext.getSql(), lifeTimerContext.getSqlId()); } return newProxy(proxyHandler); } /** * Start timer measuring {@link CallableStatement#execute() } * * @param connectionFactoryName Connection factory name * @param sql SQL query * @param sqlId SQL Id * @return Started timer context or null */ public StatementTimerContext startCallableStatementExecuteTimer(String connectionFactoryName, String sql, String sqlId) { return metricNamingStrategy.startCallableStatementExecuteTimer(connectionFactoryName, sql, sqlId); } /** * Wrap a result set to monitor it. * * @param connectionFactoryName Data source/Driver name * @param resultSet set to wrap * @param sql SQL * @param sqlId SQL Id * @return Wrapped prepared statement */ public ResultSet wrapResultSet(String connectionFactoryName, ResultSet resultSet, String sql, String sqlId) { Timer.Context lifeTimerContext = metricNamingStrategy.startResultSetTimer(connectionFactoryName, sql, sqlId); return (ResultSet) newProxy(new ResultSetProxyHandler(resultSet, getResultSetType(resultSet), connectionFactoryName, this, lifeTimerContext)); } /** * Determine the interface implemented by this result set * * @param resultSet Result set */ private Class<? extends ResultSet> getResultSetType(ResultSet resultSet) { Class<? extends ResultSet> resultSetType; if (resultSet instanceof RowSet) { if (resultSet instanceof CachedRowSet) { if (resultSet instanceof WebRowSet) { if (resultSet instanceof FilteredRowSet) { resultSetType = FilteredRowSet.class; } else if (resultSet instanceof JoinRowSet) { resultSetType = JoinRowSet.class; } else { resultSetType = WebRowSet.class; } } else { resultSetType = CachedRowSet.class; } } else if (resultSet instanceof JdbcRowSet) { resultSetType = JdbcRowSet.class; } else { resultSetType = RowSet.class; } } else { resultSetType = ResultSet.class; } return resultSetType; } }