/*
* (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* matic
*/
package org.nuxeo.ecm.core.management.jtajca.internal;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.management.ObjectInstance;
import javax.resource.spi.ManagedConnection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.connector.outbound.AbstractConnectionManager;
import org.apache.geronimo.connector.outbound.AbstractConnectionManager.Interceptors;
import org.apache.geronimo.connector.outbound.ConnectionInfo;
import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
import org.apache.log4j.MDC;
import org.nuxeo.ecm.core.management.jtajca.ConnectionPoolMonitor;
import org.nuxeo.ecm.core.storage.StorageException;
import org.nuxeo.ecm.core.storage.sql.Mapper;
import org.nuxeo.ecm.core.storage.sql.Mapper.Identification;
import org.nuxeo.ecm.core.storage.sql.SessionImpl;
import org.nuxeo.ecm.core.storage.sql.SoftRefCachingMapper;
import org.nuxeo.ecm.core.storage.sql.ra.ManagedConnectionImpl;
import org.nuxeo.runtime.metrics.MetricsService;
import org.tranql.connector.AbstractManagedConnection;
import com.codahale.metrics.JmxAttributeGauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
/**
* @author matic
*
*/
public class DefaultConnectionPoolMonitor implements ConnectionPoolMonitor {
private static final Log log = LogFactory.getLog(DefaultConnectionPoolMonitor.class);
// @since 5.7.2
protected final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
protected final String name;
protected AbstractConnectionManager cm;
protected DefaultConnectionPoolMonitor(String mame, AbstractConnectionManager cm) {
name = mame;
this.cm = enhanceConnectionManager(cm);
}
protected static Field field(Class<?> clazz, String name) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (Exception e) {
throw new RuntimeException("Cannot get access to " + clazz + "#"
+ name + " field");
}
}
@SuppressWarnings("unchecked")
protected static <T> T fetch(Field field, Object object) {
try {
return (T) field.get(object);
} catch (Exception e) {
throw new RuntimeException("Cannot get access to field content", e);
}
}
protected static void save(Field field, Object object, Object value) {
try {
field.set(object, value);
} catch (Exception e) {
throw new RuntimeException("Cannot set field content", e);
}
}
protected AbstractConnectionManager enhanceConnectionManager(
AbstractConnectionManager cm) {
if (!log.isTraceEnabled()) {
return cm;
}
Field field = field(AbstractConnectionManager.class, "interceptors");
Interceptors interceptors = fetch(field, cm);
interceptors = enhanceInterceptors(interceptors);
save(field, cm, interceptors);
return cm;
}
protected Interceptors enhanceInterceptors(Interceptors interceptors) {
Field field = field(interceptors.getClass(), "stack");
ConnectionInterceptor stack = fetch(field, interceptors);
save(field, interceptors, enhanceStack(stack));
return interceptors;
}
protected ConnectionInterceptor enhanceStack(ConnectionInterceptor stack) {
try {
Field field = field(stack.getClass(), "next");
ConnectionInterceptor next = fetch(field, stack);
save(field, stack, enhanceStack(next));
} catch (RuntimeException e) {
;
}
return (ConnectionInterceptor) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[] { ConnectionInterceptor.class }, new StackHandler(
stack));
}
protected class StackHandler implements InvocationHandler {
protected final ConnectionInterceptor stack;
public StackHandler(ConnectionInterceptor stack) {
this.stack = stack;
}
protected void traceInvoke(Method m, Object[] args) {
Throwable stackTrace = null;
if (ConnectionInterceptor.class.isAssignableFrom(m.getDeclaringClass())) {
stackTrace = new Throwable("debug stack trace");
}
log.trace("invoked " + stack.getClass().getSimpleName() + "."
+ m.getName(), stackTrace);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
return method.invoke(stack, args);
} finally {
String name = method.getName();
traceInvoke(method, args);
if (args != null && args.length > 0) {
ConnectionInfo info = (ConnectionInfo) args[0];
ManagedConnection connection = info.getManagedConnectionInfo().getManagedConnection();
IdProvider midProvider = guessProvider(connection);
if (name.startsWith("get")) {
MDC.put(midProvider.key(), midProvider.id(connection));
} else if (name.startsWith("return")) {
MDC.remove(midProvider.key());
}
}
}
}
protected IdProvider midProvider;
protected IdProvider guessProvider(ManagedConnection connection) {
if (midProvider != null) {
return midProvider;
}
if (connection instanceof ManagedConnectionImpl) {
return new IdProvider() {
@Override
public String key() {
return "vcs";
}
@Override
public Object id(ManagedConnection connection) {
return mapperId(connection);
}
};
}
if (connection instanceof AbstractManagedConnection) {
return new IdProvider() {
@Override
public String key() {
return "db";
}
@Override
public Object id(ManagedConnection connection) {
return ((AbstractManagedConnection)connection).getPhysicalConnection();
}
};
}
throw new IllegalArgumentException("unknown connection type of " + connection.getClass());
}
}
interface IdProvider {
String key();
Object id(ManagedConnection connection);
}
private static final Field SESSION_FIELD = field(
ManagedConnectionImpl.class, "session");
private static final Field WRAPPED_FIELD = field(
SoftRefCachingMapper.class, "mapper");
protected Identification mapperId(ManagedConnection connection) {
SessionImpl session = fetch(SESSION_FIELD, connection);
Mapper mapper = session.getMapper();
if (mapper instanceof SoftRefCachingMapper) {
mapper = fetch(WRAPPED_FIELD, mapper);
}
try {
return mapper.getIdentification();
} catch (StorageException e) {
log.error("Cannot fetch mapper identification", e);
return null;
}
}
protected ObjectInstance self;
@Override
public void install() {
self = DefaultMonitorComponent.bind(this, name);
registry.register(MetricRegistry.name("nuxeo", "repositories",
name, "connections", "count"), new JmxAttributeGauge(
self.getObjectName(), "ConnectionCount"));
registry.register(MetricRegistry.name("nuxeo", "repositories",
name, "connections", "idle"), new JmxAttributeGauge(
self.getObjectName(), "IdleConnectionCount"));
}
@Override
public void uninstall() {
DefaultMonitorComponent.unbind(self);
registry.remove(MetricRegistry.name("nuxeo", "repositories",
name, "connections", "count"));
registry.remove(MetricRegistry.name("nuxeo", "repositories",
name, "connections", "idle"));
self = null;
}
@Override
public int getConnectionCount() {
return cm.getConnectionCount();
}
@Override
public int getIdleConnectionCount() {
return cm.getIdleConnectionCount();
}
@Override
public int getBlockingTimeoutMilliseconds() {
return cm.getBlockingTimeoutMilliseconds();
}
@Override
public int getIdleTimeoutMinutes() {
return cm.getIdleTimeoutMinutes();
}
@Override
public int getPartitionCount() {
return cm.getPartitionCount();
}
@Override
public int getPartitionMaxSize() {
return cm.getPartitionMaxSize();
}
@Override
public void setPartitionMaxSize(int maxSize) throws InterruptedException {
cm.setPartitionMaxSize(maxSize);
}
@Override
public int getPartitionMinSize() {
return cm.getPartitionMinSize();
}
@Override
public void setPartitionMinSize(int minSize) {
cm.setPartitionMinSize(minSize);
}
@Override
public void setBlockingTimeoutMilliseconds(int timeoutMilliseconds) {
cm.setBlockingTimeoutMilliseconds(timeoutMilliseconds);
}
@Override
public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
cm.setIdleTimeoutMinutes(idleTimeoutMinutes);
}
/**
* @since 5.8
*/
public void handleNewConnectionManager(AbstractConnectionManager cm) {
this.cm = enhanceConnectionManager(cm);
}
}