/**
* Copyright © 2013 Antonin Stefanutti (antonin.stefanutti@gmail.com)
*
* 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.
*/
package io.astefanutti.metrics.cdi;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.annotation.CachedGauge;
import com.codahale.metrics.annotation.Counted;
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Gauge;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.Timed;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.interceptor.AroundConstruct;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
@Interceptor
@MetricsBinding
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
// See http://docs.oracle.com/javaee/7/tutorial/doc/interceptors.htm
/* package-private */ class MetricsInterceptor {
private final MetricRegistry registry;
private final MetricResolver resolver;
@Inject
private MetricsInterceptor(MetricRegistry registry, MetricResolver resolver) {
this.registry = registry;
this.resolver = resolver;
}
@AroundConstruct
private Object metrics(InvocationContext context) throws Exception {
// Registers the present bean constructor metrics
registerMetrics(context.getConstructor());
// Registers the present methods metrics over the bean type hierarchy
Class<?> bean = context.getConstructor().getDeclaringClass();
do {
// TODO: discover annotations declared on implemented interfaces
for (Method method : bean.getDeclaredMethods())
if (!method.isSynthetic() && !Modifier.isPrivate(method.getModifiers()))
registerMetrics(method);
bean = bean.getSuperclass();
} while (!Object.class.equals(bean));
Object target = context.proceed();
// Registers the present gauges over the bean type hierarchy after the target is constructed as it is required for the gauge invocations
bean = context.getConstructor().getDeclaringClass();
do {
// TODO: discover annotations declared on implemented interfaces
for (Method method : bean.getDeclaredMethods()) {
MetricResolver.Of<CachedGauge> cachedGauge = resolver.cachedGauge(method);
if (cachedGauge.isPresent())
registry.register(cachedGauge.metricName(), new CachingGauge(new ForwardingGauge(method, context.getTarget()), cachedGauge.metricAnnotation().timeout(), cachedGauge.metricAnnotation().timeoutUnit()));
MetricResolver.Of<Gauge> gauge = resolver.gauge(method);
if (gauge.isPresent())
registry.register(gauge.metricName(), new ForwardingGauge(method, context.getTarget()));
}
bean = bean.getSuperclass();
} while (!Object.class.equals(bean));
return target;
}
private <E extends Member & AnnotatedElement> void registerMetrics(E element) {
MetricResolver.Of<Counted> counted = resolver.counted(element);
if (counted.isPresent())
registry.counter(counted.metricName());
MetricResolver.Of<ExceptionMetered> exceptionMetered = resolver.exceptionMetered(element);
if (exceptionMetered.isPresent())
registry.meter(exceptionMetered.metricName());
MetricResolver.Of<Metered> metered = resolver.metered(element);
if (metered.isPresent())
registry.meter(metered.metricName());
MetricResolver.Of<Timed> timed = resolver.timed(element);
if (timed.isPresent())
registry.timer(timed.metricName());
}
private static final class CachingGauge extends com.codahale.metrics.CachedGauge<Object> {
private final com.codahale.metrics.Gauge<?> gauge;
private CachingGauge(com.codahale.metrics.Gauge<?> gauge, long timeout, TimeUnit timeoutUnit) {
super(timeout, timeoutUnit);
this.gauge = gauge;
}
@Override
protected Object loadValue() {
return gauge.getValue();
}
}
private static final class ForwardingGauge implements com.codahale.metrics.Gauge<Object> {
private final Method method;
private final Object object;
private ForwardingGauge(Method method, Object object) {
this.method = method;
this.object = object;
method.setAccessible(true);
}
@Override
public Object getValue() {
return invokeMethod(method, object);
}
}
private static Object invokeMethod(Method method, Object object) {
try {
return method.invoke(object);
} catch (IllegalAccessException | InvocationTargetException cause) {
throw new IllegalStateException("Error while calling method [" + method + "]", cause);
}
}
}