package rocks.inspectit.agent.java.hooking.impl;
import org.cliffc.high_scale_lib.NonBlockingHashMapLong;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rocks.inspectit.agent.java.config.impl.RegisteredSensorConfig;
import rocks.inspectit.agent.java.config.impl.SpecialSensorConfig;
import rocks.inspectit.agent.java.core.ICoreService;
import rocks.inspectit.agent.java.hooking.IConstructorHook;
import rocks.inspectit.agent.java.hooking.IHook;
import rocks.inspectit.agent.java.hooking.IHookDispatcher;
import rocks.inspectit.agent.java.hooking.IHookDispatcherMapper;
import rocks.inspectit.agent.java.hooking.IMethodHook;
import rocks.inspectit.agent.java.hooking.ISpecialHook;
import rocks.inspectit.agent.java.sensor.exception.ExceptionSensor;
import rocks.inspectit.agent.java.sensor.exception.IExceptionSensorHook;
import rocks.inspectit.agent.java.sensor.method.IMethodSensor;
import rocks.inspectit.agent.java.sensor.method.invocationsequence.InvocationSequenceSensor;
import rocks.inspectit.shared.all.instrumentation.config.impl.MethodSensorTypeConfig;
import rocks.inspectit.shared.all.spring.logger.Log;
/**
* The hook dispatching service which is called by all the hooks throughout the instrumented target
* application.
*
* @author Patrice Bouillet
* @author Eduard Tudenhoefner
* @author Ivan Senic
*/
@Component
public class HookDispatcher implements IHookDispatcherMapper, IHookDispatcher {
/**
* The logger of this class.
*/
@Log
Logger log;
/**
* The default core service.
*/
@Autowired
private ICoreService coreService;
/**
* {@link InvocationSequenceSensor} directly auto-wired.
*/
@Autowired
private InvocationSequenceSensor invocationSequenceSensor;
/**
* {@link ExceptionSensor} directly auto-wired.
*/
@Autowired
private ExceptionSensor exceptionSensor;
/**
* Contains all hooks. Using concurrent map as we need to enable thread-safety of
* {@link #addMapping(long, RegisteredSensorConfig)}.
* <p>
* Not using the {@link java.util.Map} interface on purpose, in order to use put/get methods
* with primitive longs.
*/
private final NonBlockingHashMapLong<RegisteredSensorConfig> mappings = new NonBlockingHashMapLong<RegisteredSensorConfig>();
/**
* Contains all special hooks. Using concurrent map as we need to enable thread-safety of
* {@link #addMapping(long, RegisteredSensorConfig)}.
* <p>
* Not using the {@link java.util.Map} interface on purpose, in order to use put/get methods
* with primitive longs.
*/
private final NonBlockingHashMapLong<SpecialSensorConfig> specialMappings = new NonBlockingHashMapLong<SpecialSensorConfig>();
/**
* Stores the current Status of the invocation sequence tracer in a {@link ThreadLocal} object.
*/
private final InvocationSequenceCount invocationSequenceCount = new InvocationSequenceCount();
/**
* A thread local holder object to save the current started invocation sequence.
*/
private final ThreadLocal<IHook> invocationSequenceHolder = new ThreadLocal<IHook>();
/**
* If an execution of the dispatching is already in progress, we don't dispatch anything else
* for this thread.
*/
private final ExecutionMarker executionMarker = new ExecutionMarker();
/**
* {@inheritDoc}
*/
@Override
public void addMapping(long id, RegisteredSensorConfig rsc) {
mappings.put(id, rsc);
}
/**
* {@inheritDoc}
*/
@Override
public void addMapping(long id, SpecialSensorConfig ssc) {
specialMappings.put(id, ssc);
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchMethodBeforeBody(long id, Object object, Object[] parameters) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
try {
RegisteredSensorConfig rsc = mappings.get(id);
if (rsc.isStartsInvocation()) {
// The sensor configuration contains an invocation sequence
// sensor. We have to set it on the thread local map for later
// access. Additionally, we need to save the count of the called
// invocation sensors, as another nested one could be started,
// too.
invocationSequenceCount.increment();
if (null == invocationSequenceHolder.get()) {
invocationSequenceHolder.set(invocationSequenceSensor.getHook());
}
} else if (null != invocationSequenceHolder.get()) {
// We are executing the following sensor types in an invocation
// sequence context, thus we have to execute the before body
// method of the invocation sequence hook manually.
IMethodHook invocationHook = (IMethodHook) invocationSequenceHolder.get();
// The sensor type ID is not important here, thus we are passing
// a -1. It is already stored in the data object
invocationHook.beforeBody(id, -1, object, parameters, rsc);
}
// Now iterate over all registered sensor types and execute them
// reverse execution (sensor with lowest priority first)
for (IMethodSensor methodSensor : rsc.getMethodSensorsReverse()) {
IMethodHook methodHook = (IMethodHook) methodSensor.getHook();
methodHook.beforeBody(id, methodSensor.getSensorTypeConfig().getId(), object, parameters, rsc);
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (before body)", throwable);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchFirstMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
try {
RegisteredSensorConfig rsc = mappings.get(id);
// Now iterate over all registered sensor types and execute them
// normal execution (sensor with highest priority first)
for (IMethodSensor methodSensor : rsc.getMethodSensors()) {
IMethodHook methodHook = (IMethodHook) methodSensor.getHook();
methodHook.firstAfterBody(id, methodSensor.getSensorTypeConfig().getId(), object, parameters, returnValue, rsc);
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (after body)", throwable);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchSecondMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
try {
RegisteredSensorConfig rsc = mappings.get(id);
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation
// sequence so that all data objects can be associated to that invocation
// record.
ICoreService invocCoreService = (ICoreService) invocationSequenceHolder.get();
// Now iterate over all registered sensor types and execute them
// normal execution (sensor with highest priority first)
for (IMethodSensor methodSensor : rsc.getMethodSensors()) {
IMethodHook methodHook = (IMethodHook) methodSensor.getHook();
// the invocation sequence sensor needs the original core service!
long sensorId = methodSensor.getSensorTypeConfig().getId();
if (invocCoreService == methodHook) { // NOPMD
methodHook.secondAfterBody(coreService, id, sensorId, object, parameters, returnValue, rsc);
} else {
methodHook.secondAfterBody(invocCoreService, id, sensorId, object, parameters, returnValue, rsc);
}
}
} else {
for (IMethodSensor methodSensor : rsc.getMethodSensors()) {
IMethodHook methodHook = (IMethodHook) methodSensor.getHook();
methodHook.secondAfterBody(coreService, id, methodSensor.getSensorTypeConfig().getId(), object, parameters, returnValue, rsc);
}
}
if (rsc.isStartsInvocation()) {
invocationSequenceCount.decrement();
if (0 == invocationSequenceCount.getCount()) {
invocationSequenceHolder.set(null);
}
} else if (null != invocationSequenceHolder.get()) {
// We have to execute the after body method of the invocation sequence hook
// manually.
IMethodHook invocationHook = (IMethodHook) invocationSequenceHolder.get();
// The sensor type ID is not important here, thus we are passing a -1. It is
// already stored in the data object
invocationHook.secondAfterBody(coreService, id, -1, object, parameters, returnValue, rsc);
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (second after body)", throwable);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
// rsc contains the settings for the actual method where the exception was thrown.
RegisteredSensorConfig rsc = mappings.get(id);
MethodSensorTypeConfig sensorTypeConfig = exceptionSensor.getSensorTypeConfig();
long sensorTypeId = sensorTypeConfig.getId();
ICoreService invocCoreService = null;
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation sequence so
// that all data objects can be associated to that invocation record.
invocCoreService = (ICoreService) invocationSequenceHolder.get();
}
IExceptionSensorHook exceptionHook = (IExceptionSensorHook) exceptionSensor.getHook();
if (null != invocCoreService) {
exceptionHook.dispatchOnThrowInBody(invocCoreService, id, sensorTypeId, object, exceptionObject, parameters, rsc);
} else {
exceptionHook.dispatchOnThrowInBody(coreService, id, sensorTypeId, object, exceptionObject, parameters, rsc);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchBeforeCatch(long id, Object exceptionObject) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
// rsc contains the settings of the actual method where the exception is catched.
RegisteredSensorConfig rsc = mappings.get(id);
long sensorTypeId = exceptionSensor.getSensorTypeConfig().getId();
ICoreService invocCoreService = null;
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation sequence so
// that all data objects can be associated to that invocation record.
invocCoreService = (ICoreService) invocationSequenceHolder.get();
}
IExceptionSensorHook exceptionHook = (IExceptionSensorHook) exceptionSensor.getHook();
if (null != invocCoreService) {
exceptionHook.dispatchBeforeCatchBody(invocCoreService, id, sensorTypeId, exceptionObject, rsc);
} else {
exceptionHook.dispatchBeforeCatchBody(coreService, id, sensorTypeId, exceptionObject, rsc);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchConstructorOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
// rsc contains the settings for the actual constructor where the exception was
// thrown.
RegisteredSensorConfig rsc = mappings.get(id);
long sensorTypeId = exceptionSensor.getSensorTypeConfig().getId();
ICoreService invocCoreService = null;
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation sequence so
// that all data objects can be associated to that invocation record.
invocCoreService = (ICoreService) invocationSequenceHolder.get();
}
IExceptionSensorHook exceptionHook = (IExceptionSensorHook) exceptionSensor.getHook();
if (null != invocCoreService) {
exceptionHook.dispatchOnThrowInBody(invocCoreService, id, sensorTypeId, object, exceptionObject, parameters, rsc);
} else {
exceptionHook.dispatchOnThrowInBody(coreService, id, sensorTypeId, object, exceptionObject, parameters, rsc);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchConstructorBeforeCatch(long id, Object exceptionObject) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
// rsc contains the settings of the actual constructor where the exception is
// catched.
RegisteredSensorConfig rsc = mappings.get(id);
long sensorTypeId = exceptionSensor.getSensorTypeConfig().getId();
ICoreService invocCoreService = null;
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation sequence so
// that all data objects can be associated to that invocation record.
invocCoreService = (ICoreService) invocationSequenceHolder.get();
}
IExceptionSensorHook exceptionHook = (IExceptionSensorHook) exceptionSensor.getHook();
if (null != invocCoreService) {
exceptionHook.dispatchBeforeCatchBody(invocCoreService, id, sensorTypeId, exceptionObject, rsc);
} else {
exceptionHook.dispatchBeforeCatchBody(coreService, id, sensorTypeId, exceptionObject, rsc);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchConstructorBeforeBody(long id, Object[] parameters) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
try {
RegisteredSensorConfig rsc = mappings.get(id);
if (rsc.isStartsInvocation()) {
// The sensor configuration contains an invocation sequence sensor. We have
// to set it on the thread local map for later access. Additionally, we need
// to save the count of the called invocation sensors, as another nested one
// could be started, too.
invocationSequenceCount.increment();
if (null == invocationSequenceHolder.get()) {
invocationSequenceHolder.set(invocationSequenceSensor.getHook());
}
} else if (null != invocationSequenceHolder.get()) {
// We are executing the following sensor types in an invocation sequence
// context, thus we have to execute the before body method of the invocation
// sequence hook manually.
IConstructorHook invocationHook = (IConstructorHook) invocationSequenceHolder.get();
// The sensor type ID is not important here, thus we are passing a -1. It is
// already stored in the data object
invocationHook.beforeConstructor(id, -1, parameters, rsc);
}
// Now iterate over all registered sensor types and execute them
// reverse execution (sensor with lowest priority first)
for (IMethodSensor methodSensor : rsc.getMethodSensorsReverse()) {
IConstructorHook constructorHook = (IConstructorHook) methodSensor.getHook();
constructorHook.beforeConstructor(id, methodSensor.getSensorTypeConfig().getId(), parameters, rsc);
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (before constructor)", throwable);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void dispatchConstructorAfterBody(long id, Object object, Object[] parameters) {
if (!executionMarker.isActive()) {
try {
executionMarker.active();
try {
RegisteredSensorConfig rsc = mappings.get(id);
if (null != invocationSequenceHolder.get()) {
// Need to replace the core service with the one from the invocation
// sequence so that all data objects can be associated to that invocation
// record.
ICoreService invocCoreService = (ICoreService) invocationSequenceHolder.get();
for (IMethodSensor methodSensor : rsc.getMethodSensors()) {
IConstructorHook constructorHook = (IConstructorHook) methodSensor.getHook();
// the invocation sequence sensor and the exception sensor need the
// original core service!
long sensorId = methodSensor.getSensorTypeConfig().getId();
if (invocCoreService == constructorHook) { // NOPMD
constructorHook.afterConstructor(coreService, id, sensorId, object, parameters, rsc);
} else {
constructorHook.afterConstructor(invocCoreService, id, sensorId, object, parameters, rsc);
}
}
} else {
for (IMethodSensor methodSensor : rsc.getMethodSensors()) {
IConstructorHook constructorHook = (IConstructorHook) methodSensor.getHook();
constructorHook.afterConstructor(coreService, id, methodSensor.getSensorTypeConfig().getId(), object, parameters, rsc);
}
}
if (rsc.isStartsInvocation()) {
invocationSequenceCount.decrement();
if (0 == invocationSequenceCount.getCount()) {
invocationSequenceHolder.set(null);
}
} else if (null != invocationSequenceHolder.get()) {
// We have to execute the after body method of the invocation
// sequence hook manually.
IConstructorHook invocationHook = (IConstructorHook) invocationSequenceHolder.get();
// The sensor type ID is not important here, thus we are passing
// a -1. It is already stored in the data object
invocationHook.afterConstructor(coreService, id, -1, object, parameters, rsc);
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (after constructor)", throwable);
}
} finally {
executionMarker.deactive();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Object dispatchSpecialMethodBeforeBody(long id, Object object, Object[] parameters) {
try {
SpecialSensorConfig ssc = specialMappings.get(id);
IMethodSensor methodSensor = ssc.getSensor();
ISpecialHook specialHook = (ISpecialHook) methodSensor.getHook();
Object result = specialHook.beforeBody(id, object, parameters, ssc);
if (null != result) {
return result;
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (before special method)", throwable);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Object dispatchSpecialMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue) {
try {
SpecialSensorConfig ssc = specialMappings.get(id);
IMethodSensor methodSensor = ssc.getSensor();
ISpecialHook specialHook = (ISpecialHook) methodSensor.getHook();
Object result = specialHook.afterBody(id, object, parameters, returnValue, ssc);
if (null != result) {
return result;
}
} catch (Throwable throwable) { // NOPMD
log.error("An error happened in the Hook Dispatcher! (after special method)", throwable);
}
return null;
}
/**
* Private inner class used to track the count of the started invocation sequences in one
* thread. Thus it extends {@link ThreadLocal} to provide a unique number for every Thread.
*
* @author Patrice Bouillet
*
*/
private static class InvocationSequenceCount extends ThreadLocal<Long> {
/**
* {@inheritDoc}
*/
@Override
protected Long initialValue() {
return Long.valueOf(0);
}
/**
* Increments the stored value.
*/
public void increment() {
super.set(Long.valueOf(super.get().longValue() + 1));
}
/**
* Decrements the stored value.
*/
public void decrement() {
super.set(Long.valueOf(super.get().longValue() - 1));
}
/**
* Returns the current count.
*
* @return The count.
*/
public long getCount() {
return super.get().longValue();
}
}
/**
* ThreadLocal execution marker which is used to mark executions as already in progress to not
* dispatch over and over again.
*
* @author Patrice Bouillet
*
*/
private static class ExecutionMarker extends ThreadLocal<Boolean> {
/**
* {@inheritDoc}
*/
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
/**
* Execution is active.
*/
public void active() {
super.set(Boolean.TRUE);
}
/**
* Execution is deactive.
*/
public void deactive() {
super.set(Boolean.FALSE);
}
/**
* Defines if our own execution is active, and thus we have to skip the whole processing
* (because it could happen, that we'll never end then).
*
* @return if own execution is active.
*/
public boolean isActive() {
return super.get().booleanValue();
}
}
}