/* * Copyright 2010-2014 Ning, Inc. * * Ning licenses this file to you 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 org.killbill.commons.skeleton.metrics; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.inject.Provider; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import org.killbill.commons.metrics.MetricTag; import com.codahale.metrics.MetricRegistry; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import com.sun.jersey.spi.container.ExceptionMapperContext; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * A method interceptor which times the execution of the annotated resource method. */ public class TimedResourceInterceptor implements MethodInterceptor { private final Provider<GuiceContainer> jerseyContainer; private final Provider<MetricRegistry> metricRegistry; private final String resourcePath; private final String metricName; private final String httpMethod; public TimedResourceInterceptor(final Provider<GuiceContainer> jerseyContainer, final Provider<MetricRegistry> metricRegistry, final String resourcePath, final String metricName, final String httpMethod) { this.jerseyContainer = jerseyContainer; this.metricRegistry = metricRegistry; this.resourcePath = resourcePath; this.metricName = metricName; this.httpMethod = httpMethod; } @Override public Object invoke(final MethodInvocation invocation) throws Throwable { final long startTime = System.nanoTime(); int responseStatus = 0; try { final Object response = invocation.proceed(); if (response instanceof Response) { responseStatus = ((Response) response).getStatus(); } else if (response == null || response instanceof Void) { responseStatus = Response.Status.NO_CONTENT.getStatusCode(); } else { responseStatus = Response.Status.OK.getStatusCode(); } return response; } catch (final WebApplicationException e) { responseStatus = e.getResponse().getStatus(); throw e; } catch (final Throwable e) { responseStatus = mapException(e); throw e; } finally { final long endTime = System.nanoTime(); final ResourceTimer timer = timer(invocation); timer.update(responseStatus, endTime - startTime, TimeUnit.NANOSECONDS); } } private int mapException(final Throwable e) throws Exception { final ExceptionMapperContext exceptionMapperContext = jerseyContainer.get().getWebApplication().getExceptionMapperContext(); @SuppressWarnings("unchecked") final ExceptionMapper<Throwable> exceptionMapper = exceptionMapperContext.find(e.getClass()); if (exceptionMapper != null) { return exceptionMapper.toResponse(e).getStatus(); } // If there's no mapping for it, assume 500 return Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); } private ResourceTimer timer(final MethodInvocation invocation) { final Map<String, Object> metricTags = metricTags(invocation); return new ResourceTimer(resourcePath, metricName, httpMethod, metricTags, metricRegistry.get()); } private static Map<String, Object> metricTags(final MethodInvocation invocation) { final LinkedHashMap<String, Object> metricTags = new LinkedHashMap<String, Object>(); final Method method = invocation.getMethod(); for (int i = 0; i < method.getParameterAnnotations().length; i++) { final Annotation[] parameterAnnotations = method.getParameterAnnotations()[i]; final MetricTag metricTag = findMetricTagAnnotations(parameterAnnotations); if (metricTag != null) { final Object currentArgument = invocation.getArguments()[i]; final Object tagValue; if (metricTag.property().trim().isEmpty()) { tagValue = currentArgument; } else { tagValue = getProperty(currentArgument, metricTag.property()); } metricTags.put(metricTag.tag(), tagValue); } } return metricTags; } private static MetricTag findMetricTagAnnotations(final Annotation[] parameterAnnotations) { for (final Annotation parameterAnnotation : parameterAnnotations) { if (parameterAnnotation instanceof MetricTag) { return (MetricTag) parameterAnnotation; } } return null; } private static Object getProperty(final Object currentArgument, final String property) { if (currentArgument == null) { return null; } try { final String[] methodNames = {"get" + capitalize(property), "is" + capitalize(property), property }; Method propertyMethod = null; for (final String methodName : methodNames) { try { propertyMethod = currentArgument.getClass().getMethod(methodName); break; } catch (final NoSuchMethodException e) {} } if (propertyMethod == null) { throw handleReadPropertyError(currentArgument, property, null); } return propertyMethod.invoke(currentArgument); } catch (final IllegalAccessException e) { throw handleReadPropertyError(currentArgument, property, e); } catch (final InvocationTargetException e) { throw handleReadPropertyError(currentArgument, property, e); } } private static String capitalize(final String property) { return property.substring(0, 1).toUpperCase() + property.substring(1); } private static IllegalArgumentException handleReadPropertyError(final Object object, final String property, final Exception e) { return new IllegalArgumentException(String.format("Failed to read tag property \"%s\" value from object of type %s", property, object.getClass()), e); } }