fr.xebia.management.statistics.ProfileAspect.java Source code

Java tutorial

Introduction

Here is the source code for fr.xebia.management.statistics.ProfileAspect.java

Source

/*
 * Copyright 2008-2010 Xebia and the original author or authors.
 *
 * 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 fr.xebia.management.statistics;

import java.lang.reflect.Method;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationMBeanExporter;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.support.JmxUtils;
import org.springframework.util.StringUtils;

/**
 * <p>
 * Aspect to handle the methods annotated with the {@link Profiled} annotation.
 * </p>
 * 
 * @author <a href="mailto:cyrille@cyrilleleclerc.com">Cyrille Le Clerc</a>
 */
@ManagedResource
@Aspect
public class ProfileAspect implements InitializingBean, DisposableBean, BeanNameAware, SelfNaming {

    public enum ClassNameStyle {
        COMPACT_FULLY_QUALIFIED_NAME, FULLY_QUALIFIED_NAME, SHORT_NAME
    };

    protected static final class RootObject {

        private final ProceedingJoinPoint pjp;

        private RootObject(ProceedingJoinPoint pjp) {
            super();
            this.pjp = pjp;
        }

        public Object[] getArgs() {
            return pjp.getArgs();
        }

        public Object getInvokedObject() {
            return pjp.getThis();
        }

        public Properties getSystemProperties() {
            return System.getProperties();
        }
    }

    /**
     * <p>
     * Formats the given <code>fullyQualifiedName</code> according to the given
     * <code>classNameStyle</code>.
     * </p>
     * <p>
     * Samples with <code>java.lang.String</code>:
     * <ul>
     * <li>{@link ClassNameStyle#FULLY_QUALIFIED_NAME} :
     * <code>java.lang.String</code></li>
     * <li>{@link ClassNameStyle#COMPACT_FULLY_QUALIFIED_NAME} :
     * <code>j.l.String</code></li>
     * <li>{@link ClassNameStyle#SHORT_NAME} : <code>String</code></li>
     * </ul>
     * </p>
     */
    protected static String getFullyQualifiedMethodName(String fullyQualifiedClassName, String methodName,
            ClassNameStyle classNameStyle) {
        StringBuilder fullyQualifiedMethodName = new StringBuilder(
                fullyQualifiedClassName.length() + methodName.length() + 1);
        switch (classNameStyle) {
        case FULLY_QUALIFIED_NAME:
            fullyQualifiedMethodName.append(fullyQualifiedClassName);
            break;
        case COMPACT_FULLY_QUALIFIED_NAME:
            String[] splittedFullyQualifiedName = StringUtils.delimitedListToStringArray(fullyQualifiedClassName,
                    ".");
            for (int i = 0; i < splittedFullyQualifiedName.length - 1; i++) {
                fullyQualifiedMethodName.append(splittedFullyQualifiedName[i].charAt(0)).append(".");
            }
            fullyQualifiedMethodName.append(splittedFullyQualifiedName[splittedFullyQualifiedName.length - 1]);
            break;
        case SHORT_NAME:
            fullyQualifiedMethodName.append(StringUtils.unqualify(fullyQualifiedClassName));
            break;
        default:
            // should not occur
            fullyQualifiedMethodName.append(fullyQualifiedClassName);
            break;
        }
        fullyQualifiedMethodName.append(".").append(methodName);
        return fullyQualifiedMethodName.toString();
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private ClassNameStyle classNameStyle = ClassNameStyle.COMPACT_FULLY_QUALIFIED_NAME;

    private ExpressionParser expressionParser = new SpelExpressionParser();

    /**
     * @see ObjectName#getDomain()
     */
    private String jmxDomain = "fr.xebia";

    private MBeanExporter mbeanExporter;

    private String name;

    private ObjectName objectName;

    private ParserContext parserContext = new TemplateParserContext();

    private ConcurrentMap<Method, Expression> profiledMethodNameAsExpressionByMethod = new ConcurrentHashMap<Method, Expression>();

    private MBeanServer server;

    /**
     * visible for tests
     */
    protected ConcurrentMap<String, ServiceStatistics> serviceStatisticsByName = new ConcurrentHashMap<String, ServiceStatistics>();

    public void afterPropertiesSet() throws Exception {
        if (this.server == null) {
            this.server = JmxUtils.locateMBeanServer();
        }

        this.mbeanExporter = new AnnotationMBeanExporter();
        this.mbeanExporter.setEnsureUniqueRuntimeObjectNames(false);
        this.mbeanExporter.setServer(this.server);
        this.mbeanExporter.setAutodetectMode(MBeanExporter.AUTODETECT_NONE);
        this.mbeanExporter.afterPropertiesSet();
    }

    public void destroy() throws Exception {
        this.mbeanExporter.destroy();
    }

    public MBeanExporter getMbeanExporter() {
        return mbeanExporter;
    }

    public ObjectName getObjectName() throws MalformedObjectNameException {
        if (objectName == null) {
            String objectNameAsString = jmxDomain + ":type=ProfileAspect";
            if (StringUtils.hasLength(name)) {
                objectNameAsString += ",name=" + ObjectName.quote(name);
            }
            objectName = new ObjectName(objectNameAsString);
        }
        return objectName;
    }

    @ManagedAttribute
    public int getRegisteredServiceStatisticsCount() {
        return this.serviceStatisticsByName.size();
    }

    @Around(value = "execution(* *(..)) && @annotation(profiled)", argNames = "pjp,profiled")
    public Object profileInvocation(ProceedingJoinPoint pjp, Profiled profiled) throws Throwable {

        logger.trace("> profileInvocation({},{}", pjp, profiled);

        MethodSignature jointPointSignature = (MethodSignature) pjp.getStaticPart().getSignature();

        // COMPUTE SERVICE STATISTICS NAME
        Expression nameAsExpression = profiledMethodNameAsExpressionByMethod.get(jointPointSignature.getMethod());
        if (nameAsExpression == null) {
            if (StringUtils.hasLength(profiled.name())) {
                String nameAsStringExpression = profiled.name();
                nameAsExpression = expressionParser.parseExpression(nameAsStringExpression, parserContext);
            } else {
                String fullyQualifiedMethodName = getFullyQualifiedMethodName(//
                        jointPointSignature.getDeclaringTypeName(), //
                        jointPointSignature.getName(), //
                        this.classNameStyle);
                nameAsExpression = new LiteralExpression(fullyQualifiedMethodName);
            }
        }

        String serviceStatisticsName;
        if (nameAsExpression instanceof LiteralExpression) {
            // Optimization : prevent useless objects instantiations
            serviceStatisticsName = nameAsExpression.getExpressionString();
        } else {
            serviceStatisticsName = nameAsExpression.getValue(new RootObject(pjp), String.class);
        }

        // LOOKUP SERVICE STATISTICS
        ServiceStatistics serviceStatistics = serviceStatisticsByName.get(serviceStatisticsName);

        if (serviceStatistics == null) {
            // INSTIANCIATE NEW SERVICE STATISTICS
            ServiceStatistics newServiceStatistics = new ServiceStatistics(//
                    new ObjectName(this.jmxDomain + ":type=ServiceStatistics,name=" + serviceStatisticsName), //
                    profiled.businessExceptionsTypes(), profiled.communicationExceptionsTypes());

            newServiceStatistics.setSlowInvocationThresholdInMillis(profiled.slowInvocationThresholdInMillis());
            newServiceStatistics
                    .setVerySlowInvocationThresholdInMillis(profiled.verySlowInvocationThresholdInMillis());
            int maxActive;
            if (StringUtils.hasLength(profiled.maxActiveExpression())) {
                maxActive = expressionParser.parseExpression(profiled.maxActiveExpression(), parserContext)
                        .getValue(new RootObject(pjp), Integer.class);
            } else {
                maxActive = profiled.maxActive();
            }
            newServiceStatistics.setMaxActive(maxActive);
            newServiceStatistics.setMaxActiveSemaphoreAcquisitionMaxTimeInNanos(TimeUnit.NANOSECONDS
                    .convert(profiled.maxActiveSemaphoreAcquisitionMaxTimeInMillis(), TimeUnit.MILLISECONDS));

            ServiceStatistics previousServiceStatistics = serviceStatisticsByName.putIfAbsent(serviceStatisticsName,
                    newServiceStatistics);
            if (previousServiceStatistics == null) {
                serviceStatistics = newServiceStatistics;
                mbeanExporter.registerManagedResource(serviceStatistics);
            } else {
                serviceStatistics = previousServiceStatistics;
            }
        }

        // INVOKE AND PROFILE INVOCATION
        long nanosBefore = System.nanoTime();

        Semaphore semaphore = serviceStatistics.getMaxActiveSemaphore();
        if (semaphore != null) {
            boolean acquired = semaphore.tryAcquire(
                    serviceStatistics.getMaxActiveSemaphoreAcquisitionMaxTimeInNanos(), TimeUnit.NANOSECONDS);
            if (!acquired) {
                serviceStatistics.incrementServiceUnavailableExceptionCount();
                throw new ServiceUnavailableException("Service '" + serviceStatisticsName + "' is unavailable: "
                        + serviceStatistics.getCurrentActive() + " invocations of are currently running");
            }
        }
        serviceStatistics.incrementCurrentActiveCount();
        try {

            Object returned = pjp.proceed();

            return returned;
        } catch (Throwable t) {
            serviceStatistics.incrementExceptionCount(t);
            throw t;
        } finally {
            if (semaphore != null) {
                semaphore.release();
            }
            serviceStatistics.decrementCurrentActiveCount();
            long deltaInNanos = System.nanoTime() - nanosBefore;
            serviceStatistics.incrementInvocationCounterAndTotalDurationWithNanos(deltaInNanos);
            if (logger.isDebugEnabled()) {
                logger.debug("< profileInvocation({}): {}ns", serviceStatisticsName, deltaInNanos);
            }
        }
    }

    public void setBeanName(String beanName) {
        this.name = beanName;
    }

    public void setClassNameStyle(ClassNameStyle classNameStyle) {
        this.classNameStyle = classNameStyle;
    }

    /**
     * 
     * @param classNameStyle
     *            one of COMPACT_FULLY_QUALIFIED_NAME, FULLY_QUALIFIED_NAME and
     *            SHORT_NAME
     */
    public void setClassNameStyle(String classNameStyle) {
        this.classNameStyle = ClassNameStyle.valueOf(classNameStyle);
    }

    public void setJmxDomain(String jmxDomain) {
        this.jmxDomain = jmxDomain;
    }

    public void setServer(MBeanServer server) {
        this.server = server;
    }
}