/**
* 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.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
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.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.AnnotatedMember;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessProducerField;
import javax.enterprise.inject.spi.ProcessProducerMethod;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.enterprise.util.AnnotationLiteral;
import javax.enterprise.util.Nonbinding;
import javax.interceptor.InterceptorBinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class MetricsExtension implements Extension {
private static final AnnotationLiteral<Nonbinding> NON_BINDING = new AnnotationLiteral<Nonbinding>(){};
private static final AnnotationLiteral<InterceptorBinding> INTERCEPTOR_BINDING = new AnnotationLiteral<InterceptorBinding>(){};
private static final AnnotationLiteral<MetricsBinding> METRICS_BINDING = new AnnotationLiteral<MetricsBinding>(){};
private final Map<Bean<?>, AnnotatedMember<?>> metrics = new HashMap<>();
private final MetricsConfigurationEvent configuration = new MetricsConfigurationEvent();
Set<MetricsParameter> getParameters() {
return configuration.getParameters();
}
private void addInterceptorBindings(@Observes BeforeBeanDiscovery bbd, BeanManager manager) {
declareAsInterceptorBinding(Counted.class, manager, bbd);
declareAsInterceptorBinding(ExceptionMetered.class, manager, bbd);
declareAsInterceptorBinding(Metered.class, manager, bbd);
declareAsInterceptorBinding(Timed.class, manager, bbd);
}
private <X> void metricsAnnotations(@Observes @WithAnnotations({CachedGauge.class, Counted.class, ExceptionMetered.class, Gauge.class, Metered.class, Timed.class}) ProcessAnnotatedType<X> pat) {
pat.setAnnotatedType(new AnnotatedTypeDecorator<>(pat.getAnnotatedType(), METRICS_BINDING));
}
private void metricProducerField(@Observes ProcessProducerField<? extends Metric, ?> ppf) {
metrics.put(ppf.getBean(), ppf.getAnnotatedProducerField());
}
private void metricProducerMethod(@Observes ProcessProducerMethod<? extends Metric, ?> ppm) {
// Skip the Metrics CDI alternatives
if (!ppm.getBean().getBeanClass().equals(MetricProducer.class))
metrics.put(ppm.getBean(), ppm.getAnnotatedProducerMethod());
}
private void defaultMetricRegistry(@Observes AfterBeanDiscovery abd, BeanManager manager) {
if (manager.getBeans(MetricRegistry.class).isEmpty())
abd.addBean(new MetricRegistryBean(manager));
}
private void configuration(@Observes AfterDeploymentValidation adv, BeanManager manager) {
// Fire configuration event
manager.fireEvent(configuration);
configuration.unmodifiable();
// Produce and register custom metrics
MetricRegistry registry = getReference(manager, MetricRegistry.class);
MetricName name = getReference(manager, MetricName.class);
for (Map.Entry<Bean<?>, AnnotatedMember<?>> bean : metrics.entrySet()) {
// TODO: add MetricSet metrics into the metric registry
if (bean.getKey().getTypes().contains(MetricSet.class))
continue;
registry.register(name.of(bean.getValue()), (Metric) getReference(manager, bean.getValue().getBaseType(), bean.getKey()));
}
// Let's clear the collected metric producers
metrics.clear();
}
private static <T extends Annotation> void declareAsInterceptorBinding(Class<T> annotation, BeanManager manager, BeforeBeanDiscovery bbd) {
AnnotatedType<T> annotated = manager.createAnnotatedType(annotation);
Set<AnnotatedMethod<? super T>> methods = new HashSet<>();
for (AnnotatedMethod<? super T> method : annotated.getMethods())
methods.add(new AnnotatedMethodDecorator<>(method, NON_BINDING));
bbd.addInterceptorBinding(new AnnotatedTypeDecorator<>(annotated, INTERCEPTOR_BINDING, methods));
}
private static <T> T getReference(BeanManager manager, Class<T> type) {
return getReference(manager, type, manager.resolve(manager.getBeans(type)));
}
@SuppressWarnings("unchecked")
private static <T> T getReference(BeanManager manager, Type type, Bean<?> bean) {
return (T) manager.getReference(bean, type, manager.createCreationalContext(bean));
}
}