add path-> class mapping regardless of filtering, to ensure sanity when message sampling

/*
 * Copyright (c) 2013 original 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 org.eigengo.monitor.agent.akka;

import akka.actor.*;
import akka.japi.Creator;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> anonymousActorClassName = Option.empty();
    // we sample based on actor type (i.e. a sampling rate of 'n' for a filter means 'every nth actor with type k, for each type k that matches that filter')
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters  = new ConcurrentHashMap<Option<String>, AtomicLong>();
    // we count actors by actor type (any 'anonymous' or 'generic' actors are treated as the same type)
    private final ConcurrentHashMap<Option<String>, AtomicInteger> numberOfActors = new ConcurrentHashMap<Option<String>, AtomicInteger>();
    private final ConcurrentHashMap<ActorPath, String> pathTags                   = new ConcurrentHashMap<ActorPath, String>();

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final Option<String> className = getActorClassName(actorCell, actorPath);
        final PathAndClass pathAndClass = new PathAndClass(actorPath, className);
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, className);

        // record the queue size
        this.counterInterface.recordGaugeValue(Aspects.queueSize(), actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter(Aspects.delivered(), samplingRate, tags);
        this.counterInterface.incrementCounter(Aspects.delivered(msg), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime(Aspects.actorDuration(), (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), getActorClassName(actorCell, actorCell.self().path()));
        this.counterInterface.incrementCounter(Aspects.actorError(), tags);
        this.counterInterface.incrementCounter(Aspects.actorError(failure), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), this.anonymousActorClassName);
            this.counterInterface.incrementCounter(Aspects.undelivered(), tags);
            this.counterInterface.incrementCounter(Aspects.undelivered(unhandledMessage.getMessage()), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorRefFactory} implementations
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor) : Pointcuts.anyActorOf(props) {
        recordActorCount(actor.path(), props, CountType.Increment);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        recordActorCount(actorCell.self().path(), actorCell.props(), CountType.Decrement);
    }

    /**
     * Records the actor count increment or decrement
     *
     * @param path the ActorPath being created or destroyed
     * @param props the Props of the actor at the {@code path}
     * @param countType the increment or decrement
     */
     private void recordActorCount(ActorPath path, Props props, CountType countType) {
         final Option<String> className = getActorClassName(props, path);
         final PathAndClass pac = new PathAndClass(path, getActorClassName(props, path));
         if (!includeActorPath(pac)) return;

         final String[] tags = getTags(path, className);
         this.numberOfActors.putIfAbsent(className, new AtomicInteger(0));
         // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
         final int currentNumberOfActors;
         switch (countType) {
             case Increment:
                 currentNumberOfActors = this.numberOfActors.get(className).incrementAndGet();
                 break;
             case Decrement:
                 currentNumberOfActors = this.numberOfActors.get(className).decrementAndGet();
                 break;
             default:
                 currentNumberOfActors = 0;
                 break;
         }

         this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, tags);
     }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    /**
     * The count type for exhaustive matching
     */
    private static enum CountType {
        Increment, Decrement
    }

    /**
     * Decide whether to include this ActorCell in our measurements
     *
     * @param pathAndClass the PAC container
     * @return whether to include the given actor in the metrics
     */
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        // do not monitor our own output code
        if (pathAndClass.actorClassName().isDefined()) {
            if (pathAndClass.actorClassName().get().startsWith("org.eigengo.monitor.output")) return false;
        }

        // include actor if it's in 'included' list
        if (this.agentConfiguration.included().accept(pathAndClass)) return true;
        // exclude actor if excluded (i.e. is in 'excluded' list, or excludeAllNotIncluded = true, and actor is not included)
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        // skip system actor checks if we wish to monitor them
        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    /**
     * Lookup the sample rate for an actor from the configuration
     *
     * @param pathAndClass the PAC container
     * @return the sampling rate as non-negative integer
     */
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    /**
     * Decide whether to sample an actor on a particular occasion
     *
     * @param pathAndClass the PAC container
     * @return {@code true} if we should sample this actor
     */
    private boolean sampleMessage(final PathAndClass pathAndClass) {
        final Option<String> className = pathAndClass.actorClassName();
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(className, new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(className).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    /**
     * Formats the given {@code path} in a way that can be used for tagging. For example, for
     * path akka://default/user/a/b/c, it returns akka.path:/default/user/a/b/c
     *
     * @param path the path
     * @return the tag-ready path
     */
    private String actorPathToString(ActorPath path) {
        StringBuilder sb = new StringBuilder();
        sb.append("akka.path:/");
        sb.append(path.address().system());
        for (String element : path.getElements()) {
            sb.append("/");
            sb.append(element);
        }
        return sb.toString();
    }

    /**
     * Decides whether {@code path} represents the "root" of the user actors
     *
     * @param path the actor path
     * @return {@code true} if root of user actors
     */
    private boolean isUserRoot(ActorPath path) {
        return "user".equals(path.elements().mkString("/"));
    }

    /**
     * Computes the tags for the given {@code actorPath} and {@code actor} instances.
     *
     * @param actorPath the actor path; never {@code null}
     * @param actorClassName the actor instance; may be {@code null}
     * @return non-{@code null} array of tags
     */
    private String[] getTags(final ActorPath actorPath, final Option<String> actorClassName) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This only detects "root" unnamed actors
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            final ActorPath parent = actorPath.parent();
            if (isUserRoot(parent)) {
                // the parent is akka://xxx/user
                tags.add(actorPathToString(actorPath));
            } else {
                // the parent is some other actor, akka://xxx/user/foo
                tags.add(actorPathToString(parent));
                if (this.agentConfiguration.includeRoutees()) tags.add(actorPathToString(actorPath));
            }
        } else {
            // there is no supervisor
            tags.add(actorPathToString(actorPath));
        }
        if (actorClassName.isDefined()) {
            tags.add(String.format("akka.type:%s.%s", actorPath.address().system(), actorClassName.get()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * returns the canonical name of the actor type associated with an ActorCell
     *
     * @param actorCell the actor cell to get the name for
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final ActorCell actorCell, final ActorPath actorPath) {
        return getActorClassName(actorCell.props(), actorPath);
    }

    /**
     * Returns the canonical name of the actor type associated with a Props instance
     *
     * Returns null if props.actorClass does not have a canonical name (i.e., if
     * it is a local or anonymous class)
     *
     * Notably, when the actor class is anonymous, the {@code getCanonicalName} returns {@code null},
     * which we deal with here.
     *
     * @param props the ActorRef {@code Props} instance
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final Props props, final ActorPath actorPath) {
        final String canonicalName = props.actorClass().getCanonicalName();
        if (canonicalName == null /*if actor is anonymous*/||/*OR actor is generic*/
                canonicalName.endsWith("akka.actor.Actor")) {
            // then we check to see if our path->className map can help...
            if (this.pathTags.containsKey(actorPath)) return Option.apply(this.pathTags.get(actorPath));
            return this.anonymousActorClassName;
        }
        return Option.apply(canonicalName);
    }

    /**
     * Catches actors created by the japi.Creator.create(), at a point when their type is visible to the runtime.
     * We use the visibility of the type at this one pointcut to create a map from the actor path to its type,
     * which we then access within the other pointcuts (notably on actor death and message receipt) to get the
     * 'real' type at those points.
     *
     * Since the 'ActorRefFactory.actorOf' method is guaranteed to be called on the creation of any actors after the
     * initialisation of the actorSystem itself, and since we consider anonymous/untyped actors in our count, we also
     * decrement the count of 'anonymous' actors here, for we know their type to be invisible to other pointcuts, and
     * that the element mapping path to type will not have been created (i.e. we generally assume actor paths are unique,
     * and that the actorOf advice will access the 'pathTags' map before we've inserted this new value). This approach
     * may not be robust enough -- in particular, with regards the second assumption.
     *
     * Regardless, we still double check that the map element doesn't already exist. If it does, we make the assumption
     * that it was created 'a while ago', and that the path referred to an actor of the same type.
     *
     * @param actor the Actor returned by the {@code Creator.create()} method
     * */
    after() returning(final Actor actor) : Pointcuts.actorCreator() {
        final String className = actor.getClass().getCanonicalName();
        final ActorPath actorPath = actor.self().path();
        final PathAndClass pac = new PathAndClass(actorPath, Option.apply(className));
        if (this.pathTags.containsKey(actorPath)) return; // the key is there, we need do nothing.
        // add the path -> type pair to the pathTags map
        this.pathTags.putIfAbsent(actorPath, className);

        if (includeActorPath(pac)) {
            // safe increment of the count of actors of this type
            this.numberOfActors.putIfAbsent(pac.actorClassName(), new AtomicInteger(0));
            final int currentNumberOfActors = this.numberOfActors.get(pac.actorClassName()).incrementAndGet();
            this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, pac.actorClassName()));
        }

        if (includeActorPath(new PathAndClass(actorPath, this.anonymousActorClassName))) {
            // safe decrement of the count of anonymous/untyped actors
            this.numberOfActors.putIfAbsent(this.anonymousActorClassName, new AtomicInteger(0));
            final int currentNumberOfActors = this.numberOfActors.get(this.anonymousActorClassName).decrementAndGet();
            this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, this.anonymousActorClassName));
        }
    }

}

/*
 * Copyright (c) 2013 original 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 org.eigengo.monitor.agent.akka;

import akka.actor.*;
import akka.japi.Creator;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> anonymousActorClassName = Option.empty();
    // we sample based on actor type (i.e. a sampling rate of 'n' for a filter means 'every nth actor with type k, for each type k that matches that filter')
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters  = new ConcurrentHashMap<Option<String>, AtomicLong>();
    // we count actors by actor type (any 'anonymous' or 'generic' actors are treated as the same type)
    private final ConcurrentHashMap<Option<String>, AtomicInteger> numberOfActors = new ConcurrentHashMap<Option<String>, AtomicInteger>();
    private final ConcurrentHashMap<ActorPath, String> pathTags                   = new ConcurrentHashMap<ActorPath, String>();

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final Option<String> className = getActorClassName(actorCell, actorPath);
        final PathAndClass pathAndClass = new PathAndClass(actorPath, className);
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, className);

        // record the queue size
        this.counterInterface.recordGaugeValue(Aspects.queueSize(), actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter(Aspects.delivered(), samplingRate, tags);
        this.counterInterface.incrementCounter(Aspects.delivered(msg), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime(Aspects.actorDuration(), (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), getActorClassName(actorCell, actorCell.self().path()));
        this.counterInterface.incrementCounter(Aspects.actorError(), tags);
        this.counterInterface.incrementCounter(Aspects.actorError(failure), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), this.anonymousActorClassName);
            this.counterInterface.incrementCounter(Aspects.undelivered(), tags);
            this.counterInterface.incrementCounter(Aspects.undelivered(unhandledMessage.getMessage()), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorRefFactory} implementations
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor) : Pointcuts.anyActorOf(props) {
        recordActorCount(actor.path(), props, CountType.Increment);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        recordActorCount(actorCell.self().path(), actorCell.props(), CountType.Decrement);
    }

    /**
     * Records the actor count increment or decrement
     *
     * @param path the ActorPath being created or destroyed
     * @param props the Props of the actor at the {@code path}
     * @param countType the increment or decrement
     */
     private void recordActorCount(ActorPath path, Props props, CountType countType) {
         final Option<String> className = getActorClassName(props, path);
         final PathAndClass pac = new PathAndClass(path, getActorClassName(props, path));
         if (!includeActorPath(pac)) return;

         final String[] tags = getTags(path, className);
         this.numberOfActors.putIfAbsent(className, new AtomicInteger(0));
         // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
         final int currentNumberOfActors;
         switch (countType) {
             case Increment:
                 currentNumberOfActors = this.numberOfActors.get(className).incrementAndGet();
                 break;
             case Decrement:
                 currentNumberOfActors = this.numberOfActors.get(className).decrementAndGet();
                 break;
             default:
                 currentNumberOfActors = 0;
                 break;
         }

         this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, tags);
     }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    /**
     * The count type for exhaustive matching
     */
    private static enum CountType {
        Increment, Decrement
    }

    /**
     * Decide whether to include this ActorCell in our measurements
     *
     * @param pathAndClass the PAC container
     * @return whether to include the given actor in the metrics
     */
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        // do not monitor our own output code
        if (pathAndClass.actorClassName().isDefined()) {
            if (pathAndClass.actorClassName().get().startsWith("org.eigengo.monitor.output")) return false;
        }

        // include actor if it's in 'included' list
        if (this.agentConfiguration.included().accept(pathAndClass)) return true;
        // exclude actor if excluded (i.e. is in 'excluded' list, or excludeAllNotIncluded = true, and actor is not included)
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        // skip system actor checks if we wish to monitor them
        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    /**
     * Lookup the sample rate for an actor from the configuration
     *
     * @param pathAndClass the PAC container
     * @return the sampling rate as non-negative integer
     */
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    /**
     * Decide whether to sample an actor on a particular occasion
     *
     * @param pathAndClass the PAC container
     * @return {@code true} if we should sample this actor
     */
    private boolean sampleMessage(final PathAndClass pathAndClass) {
        final Option<String> className = pathAndClass.actorClassName();
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(className, new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(className).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    /**
     * Formats the given {@code path} in a way that can be used for tagging. For example, for
     * path akka://default/user/a/b/c, it returns akka.path:/default/user/a/b/c
     *
     * @param path the path
     * @return the tag-ready path
     */
    private String actorPathToString(ActorPath path) {
        StringBuilder sb = new StringBuilder();
        sb.append("akka.path:/");
        sb.append(path.address().system());
        for (String element : path.getElements()) {
            sb.append("/");
            sb.append(element);
        }
        return sb.toString();
    }

    /**
     * Decides whether {@code path} represents the "root" of the user actors
     *
     * @param path the actor path
     * @return {@code true} if root of user actors
     */
    private boolean isUserRoot(ActorPath path) {
        return "user".equals(path.elements().mkString("/"));
    }

    /**
     * Computes the tags for the given {@code actorPath} and {@code actor} instances.
     *
     * @param actorPath the actor path; never {@code null}
     * @param actorClassName the actor instance; may be {@code null}
     * @return non-{@code null} array of tags
     */
    private String[] getTags(final ActorPath actorPath, final Option<String> actorClassName) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This only detects "root" unnamed actors
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            final ActorPath parent = actorPath.parent();
            if (isUserRoot(parent)) {
                // the parent is akka://xxx/user
                tags.add(actorPathToString(actorPath));
            } else {
                // the parent is some other actor, akka://xxx/user/foo
                tags.add(actorPathToString(parent));
                if (this.agentConfiguration.includeRoutees()) tags.add(actorPathToString(actorPath));
            }
        } else {
            // there is no supervisor
            tags.add(actorPathToString(actorPath));
        }
        if (actorClassName.isDefined()) {
            tags.add(String.format("akka.type:%s.%s", actorPath.address().system(), actorClassName.get()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * returns the canonical name of the actor type associated with an ActorCell
     *
     * @param actorCell the actor cell to get the name for
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final ActorCell actorCell, final ActorPath actorPath) {
        return getActorClassName(actorCell.props(), actorPath);
    }

    /**
     * Returns the canonical name of the actor type associated with a Props instance
     *
     * Returns null if props.actorClass does not have a canonical name (i.e., if
     * it is a local or anonymous class)
     *
     * Notably, when the actor class is anonymous, the {@code getCanonicalName} returns {@code null},
     * which we deal with here.
     *
     * @param props the ActorRef {@code Props} instance
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final Props props, final ActorPath actorPath) {
        final String canonicalName = props.actorClass().getCanonicalName();
        if (canonicalName == null /*if actor is anonymous*/||/*OR actor is generic*/
                canonicalName.endsWith("akka.actor.Actor")) {
            // then we check to see if our path->className map can help...
            if (this.pathTags.containsKey(actorPath)) return Option.apply(this.pathTags.get(actorPath));
            return this.anonymousActorClassName;
        }
        return Option.apply(canonicalName);
    }

    /**
     * Catches actors created by the japi.Creator.create(), at a point when their type is visible to the runtime.
     * We use the visibility of the type at this one pointcut to create a map from the actor path to its type,
     * which we then access within the other pointcuts (notably on actor death and message receipt) to get the
     * 'real' type at those points.
     *
     * Since the 'ActorRefFactory.actorOf' method is guaranteed to be called on the creation of any actors after the
     * initialisation of the actorSystem itself, and since we consider anonymous/untyped actors in our count, we also
     * decrement the count of 'anonymous' actors here, for we know their type to be invisible to other pointcuts, and
     * that the element mapping path to type will not have been created (i.e. we generally assume actor paths are unique,
     * and that the actorOf advice will access the 'pathTags' map before we've inserted this new value). This approach
     * may not be robust enough -- in particular, with regards the second assumption.
     *
     * Regardless, we still double check that the map element doesn't already exist. If it does, we make the assumption
     * that it was created 'a while ago', and that the path referred to an actor of the same type.
     *
     * @param actor the Actor returned by the {@code Creator.create()} method
     * */
    after() returning(final Actor actor) : Pointcuts.actorCreator() {
        final String className = actor.getClass().getCanonicalName();
        final ActorPath actorPath = actor.self().path();
        final PathAndClass pac = new PathAndClass(actorPath, Option.apply(className));
        if (this.pathTags.containsKey(actorPath)) return; // the key is there, we need do nothing.

        if (includeActorPath(pac)) {
            // add the path -> type pair to the pathTags map
            this.pathTags.putIfAbsent(actorPath, className);

            // safe increment of the count of actors of this type
            this.numberOfActors.putIfAbsent(pac.actorClassName(), new AtomicInteger(0));
            final int currentNumberOfActors = this.numberOfActors.get(pac.actorClassName()).incrementAndGet();
            this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, pac.actorClassName()));
        }

        if (includeActorPath(new PathAndClass(actorPath, this.anonymousActorClassName))) {
            // safe decrement of the count of anonymous/untyped actors
            this.numberOfActors.putIfAbsent(this.anonymousActorClassName, new AtomicInteger(0));
            final int currentNumberOfActors = this.numberOfActors.get(this.anonymousActorClassName).decrementAndGet();
            this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, this.anonymousActorClassName));
        }
    }

}


Commenting out debug pointcut because it causes class sizes to balloon.

git-svn-id: 2c527eb49caa05e19d6b2be874bf74fa9d7ea670@614620 13f79535-47bb-0310-9956-ffa450edef68

package org.apache.maven;

import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.DefaultPluginManager;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.aspectj.lang.JoinPoint;

import java.util.Iterator;
import java.util.List;

public aspect CoreDebuggingAspect
{

//    after() throwing ( RuntimeException e ):
////        adviceexecution( )
////        && args( jp )
//        call( * *..*.*(..))
//        && !within( CoreDebuggingAspect+ )
//        && !handler( * )
//    {
//        System.out.println( "Error: " + e.getClass().getName() + "\nwas in join point: " + thisJoinPoint.toLongString() + "\n(at: " + thisJoinPoint.getSourceLocation() + ")" );
//    }

//    after( MavenExecutionRequest request ) returning( List projects ):
//        call( List DefaultMaven.getProjects( MavenExecutionRequest ) )
//        && args( request )
//    {
//        System.out.println( "Got projects-list of size " + ( projects == null ? "null" : "" + projects.size() ) + ":\n\n" + projects );
//    }

//    private ClassRealm pluginRealm;
//
//    after() returning( ClassRealm realm ):
//        call( ClassRealm PluginDescriptor.getClassRealm() )
//        && cflow( execution( * DefaultPluginManager.executeMojo( .. ) ) )
//    {
//        pluginRealm = realm;
//    }
//
//    after():
//        execution( * DefaultPluginManager.executeMojo( .. ) )
//    {
//        pluginRealm = null;
//    }
//
//    void around():
//        call( void Mojo+.execute( .. ) )
//    {
//        try
//        {
//            proceed();
//        }
//        catch( Error err )
//        {
//            System.out.println( "Plugin realm was " + pluginRealm + ":\n\n\n" );
//            pluginRealm.display();
//
//            throw err;
//        }
//    }
//
//    after() returning( List reports ):
//        cflow( execution( * PluginParameterExpressionEvaluator.evaluate( .. ) ) )
//        && call( List MavenSession.getReports() )
//    {
//        System.out.println( "Injecting reports for ${reports} expression.\n\n" );
//        if ( reports != null && !reports.isEmpty() )
//        {
//            for ( Iterator it = reports.iterator(); it.hasNext(); )
//            {
//                Object report = it.next();
//                System.out.println( "Report: " + report + " has classloader:\n" + report.getClass().getClassLoader() );
//            }
//        }
//        System.out.println( "\n\n" );
//    }

}

package org.apache.maven;

import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.DefaultPluginManager;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.aspectj.lang.JoinPoint;

import java.util.Iterator;
import java.util.List;

public aspect CoreDebuggingAspect
{

    after() throwing ( RuntimeException e ):
//        adviceexecution( )
//        && args( jp )
        call( * *..*.*(..))
        && !within( CoreDebuggingAspect+ )
        && !handler( * )
    {
        System.out.println( "Error: " + e.getClass().getName() + "\nwas in join point: " + thisJoinPoint.toLongString() + "\n(at: " + thisJoinPoint.getSourceLocation() + ")" );
    }

//    after( MavenExecutionRequest request ) returning( List projects ):
//        call( List DefaultMaven.getProjects( MavenExecutionRequest ) )
//        && args( request )
//    {
//        System.out.println( "Got projects-list of size " + ( projects == null ? "null" : "" + projects.size() ) + ":\n\n" + projects );
//    }

//    private ClassRealm pluginRealm;
//
//    after() returning( ClassRealm realm ):
//        call( ClassRealm PluginDescriptor.getClassRealm() )
//        && cflow( execution( * DefaultPluginManager.executeMojo( .. ) ) )
//    {
//        pluginRealm = realm;
//    }
//
//    after():
//        execution( * DefaultPluginManager.executeMojo( .. ) )
//    {
//        pluginRealm = null;
//    }
//
//    void around():
//        call( void Mojo+.execute( .. ) )
//    {
//        try
//        {
//            proceed();
//        }
//        catch( Error err )
//        {
//            System.out.println( "Plugin realm was " + pluginRealm + ":\n\n\n" );
//            pluginRealm.display();
//
//            throw err;
//        }
//    }
//
//    after() returning( List reports ):
//        cflow( execution( * PluginParameterExpressionEvaluator.evaluate( .. ) ) )
//        && call( List MavenSession.getReports() )
//    {
//        System.out.println( "Injecting reports for ${reports} expression.\n\n" );
//        if ( reports != null && !reports.isEmpty() )
//        {
//            for ( Iterator it = reports.iterator(); it.hasNext(); )
//            {
//                Object report = it.next();
//                System.out.println( "Report: " + report + " has classloader:\n" + report.getClass().getClassLoader() );
//            }
//        }
//        System.out.println( "\n\n" );
//    }

}


AbstractPalavaAspect now appends (instead of prepends) new modules

/**
 * palava - a java-php-bridge
 * Copyright (C) 2007-2010  CosmoCode GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package de.cosmocode.palava.core.aop;

import java.util.Collections;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;

public abstract aspect AbstractPalavaAspect {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractPalavaAspect.class);

    private final Module module = new Module() {

        @Override
        public void configure(Binder binder) {
            final AbstractPalavaAspect self = AbstractPalavaAspect.this;
            LOG.trace("Injecting members on {}", self);
            Preconditions.checkState(!alreadyInjected, "Members have been already injected on {}", self);
            binder.requestInjection(self);
            alreadyInjected = true;
        }

    };

    private boolean alreadyInjected;

    private Module[] append(Module[] modules) {
        final Module[] array = new Module[modules.length + 1];
        System.arraycopy(modules, 0, array, 0, modules.length);
        array[modules.length] = module;
        return array;
    }

    private Iterable<? extends Module> append(Iterable<? extends Module> modules) {
        return Iterables.concat(modules, Collections.singleton(module));
    }

    @SuppressAjWarnings("adviceDidNotMatch")
    Injector around(Module[] modules): call(Injector Guice.createInjector(Module...)) && args(modules) {
        return proceed(append(modules));
    }

    @SuppressAjWarnings("adviceDidNotMatch")
    Injector around(Iterable<? extends Module> modules): call(Injector Guice.createInjector(Iterable<? extends Module>)) && args(modules) {
        return proceed(append(modules));
    }

    @SuppressAjWarnings("adviceDidNotMatch")
    Injector around(Stage stage, Module[] modules): call(Injector Guice.createInjector(Stage, Module...)) && args(stage, modules) {
        return proceed(stage, append(modules));
    }

    @SuppressAjWarnings("adviceDidNotMatch")
    Injector around(Stage stage, Iterable<? extends Module> modules): call(Injector Guice.createInjector(Stage, Iterable<? extends Module>)) && args(stage, modules) {
        return proceed(stage, append(modules));
    }

    protected final void checkState() {
        Preconditions.checkState(alreadyInjected, "Members have not been injected on {}", this);
    }

}

/**
 * palava - a java-php-bridge
 * Copyright (C) 2007-2010  CosmoCode GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package de.cosmocode.palava.core.aop;

import java.util.Collections;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;

public abstract aspect AbstractPalavaAspect {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractPalavaAspect.class);

    private final Module module = new Module() {

        @Override
        public void configure(Binder binder) {
            final AbstractPalavaAspect self = AbstractPalavaAspect.this;
            LOG.trace("Injecting members on {}", self);
            Preconditions.checkState(!alreadyInjected, "Members have been already injected on {}", self);
            binder.requestInjection(self);
            alreadyInjected = true;
        }

    };

    private boolean alreadyInjected;

    private Module[] append(Module[] modules) {
        final Module[] array = new Module[modules.length + 1];
        array[0] = module;
        System.arraycopy(modules, 0, array, 1, modules.length);
        return array;
    }

    private Iterable<? extends Module> append(Iterable<? extends Module> modules) {
        return Iterables.concat(Collections.singleton(module), modules);
    }

    Injector around(Module[] modules): call(Injector Guice.createInjector(Module...)) && args(modules) {
        return proceed(append(modules));
    }

    Injector around(Iterable<? extends Module> modules): call(Injector Guice.createInjector(Iterable<? extends Module>)) && args(modules) {
        return proceed(append(modules));
    }

    Injector around(Stage stage, Module[] modules): call(Injector Guice.createInjector(Stage, Module...)) && args(stage, modules) {
        return proceed(stage, append(modules));
    }

    Injector around(Stage stage, Iterable<? extends Module> modules): call(Injector Guice.createInjector(Stage, Iterable<? extends Module>)) && args(stage, modules) {
        return proceed(stage, append(modules));
    }

    protected final void checkState() {
        Preconditions.checkState(alreadyInjected, "Members have not been injected on {}", this);
    }

}


Remove outdated JpaExceptionTranslatorAspect

Issue: SPR-15047

/*
 * Copyright 2002-2014 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 org.springframework.orm.jpa.aspectj;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

import org.aspectj.lang.annotation.SuppressAjWarnings;

import org.springframework.dao.DataAccessException;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;

public aspect JpaExceptionTranslatorAspect {

	pointcut entityManagerCall():
		call(* EntityManager.*(..)) || call(* EntityManagerFactory.*(..)) ||
		call(* EntityTransaction.*(..)) || call(* Query.*(..));

	@SuppressAjWarnings("adviceDidNotMatch")
	after() throwing(RuntimeException re): entityManagerCall() {
		DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(re);
		if (dex != null) {
			throw dex;
		}
		else {
			throw re;
		}
	}

}


Javadoc and log message tweaks.

Javadoc and log message tweaks.

/*
 * Copyright (C) 2011 Archie L. Cobbs and other authors. All rights reserved.
 *
 * $Id: ThreadConfigurableAspect.aj 282 2012-02-16 20:48:06Z archie.cobbs $
 */

package org.dellroad.stuff.spring;

import java.io.ObjectStreamException;
import java.io.Serializable;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect;
import org.springframework.beans.factory.wiring.BeanConfigurerSupport;
import org.springframework.beans.factory.wiring.BeanWiringInfoResolver;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Aspect that autowires classes marked with the {@link ThreadConfigurable @ThreadConfigurable} annotation using
 * the per-thread Spring application context configured in the {@link ThreadConfigurableContextHolder} singleton.
 *
 * <p>
 * This implementation is derived from Spring's {@code AnnotationBeanConfigurerAspect} implementation
 * and therefore shares its license (also Apache).
 * </p>
 *
 * @see ThreadConfigurable
 * @see ThreadConfigurableContextHolder
 */
public aspect ThreadConfigurableAspect extends AbstractConfigurableAspect {

// Stuff copied from AbstractInterfaceDrivenDependencyInjectionAspect.aj

    public pointcut beanConstruction(Object bean) :
        initialization(ThreadConfigurableObject+.new(..)) && this(bean);

    public pointcut beanDeserialization(Object bean) :
        execution(Object ThreadConfigurableDeserializationSupport+.readResolve()) &&
        this(bean);

    public pointcut leastSpecificSuperTypeConstruction() : initialization(ThreadConfigurableObject.new(..));

    declare parents:
        ThreadConfigurableObject+ && Serializable+ implements ThreadConfigurableDeserializationSupport;

    static interface ThreadConfigurableDeserializationSupport extends Serializable {
    }

    public Object ThreadConfigurableDeserializationSupport.readResolve() throws ObjectStreamException {
        return this;
    }

// Stuff copied from AnnotationBeanConfigurerAspect.aj

    public pointcut inConfigurableBean() : @this(ThreadConfigurable);

    public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*);

    declare parents: @ThreadConfigurable * implements ThreadConfigurableObject;

    private pointcut preConstructionConfigurationSupport(ThreadConfigurable c) : @this(c) && if(c.preConstruction());

	declare parents: @ThreadConfigurable Serializable+ implements ThreadConfigurableDeserializationSupport;

// Our implementation

    @Override
    protected BeanFactory getBeanFactory(Object bean) {

        // Get ThreadConfigurableContextHolder singleton
        ThreadConfigurableContextHolder threadConfigurableContextHolder = ThreadConfigurableContextHolder.getInstance();
        if (threadConfigurableContextHolder == null) {
            if (this.log.isDebugEnabled()) {
                this.log.debug("can't configure @" + ThreadConfigurable.class.getName()
                  + "-annotated bean of type " + bean.getClass().getName()
                  + " because the ThreadConfigurableContextHolder singleton instance has been set to null;"
                  + " proceeding without injection");
            }
            return null;
        }

        // Get ConfigurableApplicationContext
        ConfigurableApplicationContext context = threadConfigurableContextHolder.get();
        if (context == null) {
            if (this.log.isDebugEnabled()) {
                this.log.debug("can't configure @" + ThreadConfigurable.class.getName()
                  + "-annotated bean of type " + bean.getClass().getName()
                  + " because no ConfigurableApplicationContext has been configured for the current thread via "
                  + ThreadConfigurableContextHolder.class.getName() + ".set() nor has a default been set;"
                  + " proceeding without injection");
            }
            return null;
        }

        // Return associated BeanFactory
        return context.getBeanFactory();
    }

    @Override
    protected BeanWiringInfoResolver getBeanWiringInfoResolver(Object bean) {
        return new ThreadConfigurableBeanWiringInfoResolver();
    }
}

/*
 * Copyright (C) 2011 Archie L. Cobbs and other authors. All rights reserved.
 *
 * $Id: ThreadConfigurableAspect.aj 282 2012-02-16 20:48:06Z archie.cobbs $
 */

package org.dellroad.stuff.spring;

import java.io.ObjectStreamException;
import java.io.Serializable;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect;
import org.springframework.beans.factory.wiring.BeanConfigurerSupport;
import org.springframework.beans.factory.wiring.BeanWiringInfoResolver;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Aspect that autowires classes marked with the {@link ThreadConfigurable @ThreadConfigurable} annotation using
 * the Spring application context returned by {@link ThreadConfigurableContextHolder#get()}.
 *
 * <p>
 * This implementation is derived from Spring's {@code AnnotationBeanConfigurerAspect} implementation
 * and therefore shares its license (also Apache).
 * </p>
 *
 * @see ThreadConfigurable
 * @see ThreadConfigurableContextHolder
 */
public aspect ThreadConfigurableAspect extends AbstractConfigurableAspect {

// Stuff copied from AbstractInterfaceDrivenDependencyInjectionAspect.aj

    public pointcut beanConstruction(Object bean) :
        initialization(ThreadConfigurableObject+.new(..)) && this(bean);

    public pointcut beanDeserialization(Object bean) :
        execution(Object ThreadConfigurableDeserializationSupport+.readResolve()) &&
        this(bean);

    public pointcut leastSpecificSuperTypeConstruction() : initialization(ThreadConfigurableObject.new(..));

    declare parents:
        ThreadConfigurableObject+ && Serializable+ implements ThreadConfigurableDeserializationSupport;

    static interface ThreadConfigurableDeserializationSupport extends Serializable {
    }

    public Object ThreadConfigurableDeserializationSupport.readResolve() throws ObjectStreamException {
        return this;
    }

// Stuff copied from AnnotationBeanConfigurerAspect.aj

    public pointcut inConfigurableBean() : @this(ThreadConfigurable);

    public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*);

    declare parents: @ThreadConfigurable * implements ThreadConfigurableObject;

    private pointcut preConstructionConfigurationSupport(ThreadConfigurable c) : @this(c) && if(c.preConstruction());

	declare parents: @ThreadConfigurable Serializable+ implements ThreadConfigurableDeserializationSupport;

// Our implementation

    @Override
    protected BeanFactory getBeanFactory(Object bean) {

        // Get ThreadConfigurableContextHolder singleton
        ThreadConfigurableContextHolder threadConfigurableContextHolder = ThreadConfigurableContextHolder.getInstance();
        if (threadConfigurableContextHolder == null) {
            if (this.log.isDebugEnabled()) {
                this.log.debug("can't configure @" + ThreadConfigurable.class.getName()
                  + "-annotated bean of type " + bean.getClass().getName()
                  + " because the ThreadConfigurableContextHolder singleton instance has been set to null;"
                  + " proceeding without injection");
            }
            return null;
        }

        // Get ConfigurableApplicationContext
        ConfigurableApplicationContext context = threadConfigurableContextHolder.get();
        if (context == null) {
            if (this.log.isDebugEnabled()) {
                this.log.debug("can't configure @" + ThreadConfigurable.class.getName()
                  + "-annotated bean of type " + bean.getClass().getName()
                  + " because no ConfigurableApplicationContext has been configured for the current thread via "
                  + ThreadConfigurableContextHolder.class.getName() + ".set() nor has a default"
                  + " ConfigurableApplicationContext has been set; proceeding without injection");
            }
            return null;
        }

        // Return associated BeanFactory
        return context.getBeanFactory();
    }

    @Override
    protected BeanWiringInfoResolver getBeanWiringInfoResolver(Object bean) {
        return new ThreadConfigurableBeanWiringInfoResolver();
    }
}


Clean

Clean

package org.parallelj.launching.errors;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.parallelj.internal.MessageKind;
import org.parallelj.internal.kernel.KProcess;
import org.parallelj.internal.kernel.KProgram;
import org.parallelj.internal.kernel.procedure.CallableProcedure;
import org.parallelj.internal.kernel.procedure.RunnableProcedure;
import org.parallelj.launching.OnError;

privileged public aspect ProceduresOnErrorManagement {

	declare parents: org.parallelj.internal.kernel.KProgram implements IProceduresOnError;

	/*
	 * Add the interface IProceduresOnError to the KProgram
	*/
	public Method IProceduresOnError.getterMethod;
	public String IProceduresOnError.fieldName;
	private boolean IProceduresOnError.isErrors=false;
	private boolean IProceduresOnError.isHandledErrors=false;

	public void IProceduresOnError.setGetterMethod(Method getterFieldMethod) {
		this.getterMethod = getterFieldMethod;
	}

	public void IProceduresOnError.setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	public String IProceduresOnError.getFieldName() {
		return this.fieldName;
	}

	public Method IProceduresOnError.getGetterMethod() {
		return this.getterMethod;
	}

	public synchronized void IProceduresOnError.addProcedureInError(KProgram kprogram, Object program, Object procedure, Exception exception) {
		ProceduresOnError obj;

		this.isErrors=true;
		Method method = ((IProceduresOnError)kprogram).getGetterMethod();
		if (method != null) {
			try {
				obj = (ProceduresOnError)method.invoke(program, new Object[]{});
				if (obj==null) {
					obj = new ProceduresOnError();
					Field field = program.getClass().getDeclaredField(((IProceduresOnError)kprogram).getFieldName());
					field.setAccessible(true);
					field.set(program, obj);
				}
				obj.addProcedureInError(procedure, exception);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public synchronized void IProceduresOnError.addProcedureHandledInError(KProgram kprogram, Object program, Object procedure, Exception exception) {
		ProceduresOnError obj;

		this.isHandledErrors=true;
		Method method = ((IProceduresOnError)kprogram).getGetterMethod();
		if (method != null) {
			try {
				obj = (ProceduresOnError)method.invoke(program, new Object[]{});
				if (obj==null) {
					obj = new ProceduresOnError();
					Field field = program.getClass().getDeclaredField(((IProceduresOnError)kprogram).getFieldName());
					field.setAccessible(true);
					field.set(program, obj);
				}
				obj.addProcedureHandledInError(procedure, exception);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public synchronized ProceduresOnError IProceduresOnError.getAllProceduresInError(Object program) {
		ProceduresOnError obj=null;
		if (this.getGetterMethod()!=null) {
			try {
				obj = (ProceduresOnError)this.getGetterMethod().invoke(program, new Object[]{});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return obj;
	}

	public boolean IProceduresOnError.isError() {
		return this.isErrors;
	}

	public boolean IProceduresOnError.isHandledError() {
		return this.isHandledErrors;
	}

    pointcut procsOnError(Object a): get(@OnError ProceduresOnError *.*) && this(a);

    before(Object procOnErrorField): procsOnError(procOnErrorField) {
    }

	/*
	 * Allow to get the procedures in error of a Process
	 */
	public static synchronized ProceduresOnError getProceduresInErrors(
			org.parallelj.mirror.Process process) {
		return ((IProceduresOnError)process.getProgram()).getAllProceduresInError(process.getContext());
	}

	// Interception around Runnable execution
	void around(Object self): call(void complete())
		&& target(org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall)
		&& this(self)  {

		RunnableProcedure.RunnableCall runnable = (RunnableProcedure.RunnableCall)thisJoinPoint.getTarget();
		RunnableProcedure procedure = (RunnableProcedure)runnable.getProcedure();
		if (runnable.getException() != null) {
			// There is an error !!!
			KProcess process = runnable.getProcess();
			if(procedure.getHandler() == null) {
				((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), runnable.getContext(), runnable.getException());
			} else {
				((IProceduresOnError)process.getProgram()).addProcedureHandledInError(process.getProgram(), process.getContext(), runnable.getContext(), runnable.getException());
			}
		}
		proceed(self);
	}

	// Interception around Callable execution
	void around(Object self): call(void complete())
		&& target(org.parallelj.internal.kernel.procedure.CallableProcedure.CallableCall)
		&& this(self)  {

		CallableProcedure.CallableCall callable = (CallableProcedure.CallableCall)thisJoinPoint.getTarget();
		CallableProcedure procedure = (CallableProcedure)callable.getProcedure();
		if (callable.getException() != null) {
			// There is an error !!!
			KProcess process = callable.getProcess();
			if(procedure.getHandler() == null) {
				((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), callable.getContext(), callable.getException());
			} else {
				((IProceduresOnError)process.getProgram()).addProcedureHandledInError(process.getProgram(), process.getContext(), callable.getContext(), callable.getException());			}
		}
		proceed(self);
	}

	boolean around(org.parallelj.internal.kernel.KJoin self, org.parallelj.internal.kernel.KProcess process)
		:execution(* org.parallelj.internal.kernel.KJoin+.isEnabled(..))
		&& this(self) && args(process)
		&& within(org.parallelj.internal.kernel.loop.KWhileLoop) {

		try {
			return proceed(self, process);
		} catch (Exception e) {
			((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), process.getContext(), e);
			MessageKind.E0005.format(process.getContext(), e);
			return false;
		}

	}

}

package org.parallelj.launching.errors;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.parallelj.internal.MessageKind;
import org.parallelj.internal.kernel.KCall;
import org.parallelj.internal.kernel.KJoin;
import org.parallelj.internal.kernel.KProcess;
import org.parallelj.internal.kernel.KProgram;
import org.parallelj.internal.kernel.KSplit;
import org.parallelj.internal.kernel.loop.KWhileLoop;
import org.parallelj.internal.kernel.procedure.CallableProcedure;
import org.parallelj.internal.kernel.procedure.RunnableProcedure;
import org.parallelj.launching.OnError;
import org.parallelj.launching.errors.IProceduresOnError;

import debug.Debug;

privileged public aspect ProceduresOnErrorManagement {

	declare parents: org.parallelj.internal.kernel.KProgram implements IProceduresOnError;

	/*
	 * Add the interface IProceduresOnError to the KProgram
	*/
	public Method IProceduresOnError.getterMethod;
	public String IProceduresOnError.fieldName;
	private boolean IProceduresOnError.isErrors=false;
	private boolean IProceduresOnError.isHandledErrors=false;

	public void IProceduresOnError.setGetterMethod(Method getterFieldMethod) {
		this.getterMethod = getterFieldMethod;
	}

	public void IProceduresOnError.setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	public String IProceduresOnError.getFieldName() {
		return this.fieldName;
	}

	public Method IProceduresOnError.getGetterMethod() {
		return this.getterMethod;
	}

	public synchronized void IProceduresOnError.addProcedureInError(KProgram kprogram, Object program, Object procedure, Exception exception) {
		ProceduresOnError obj;

		this.isErrors=true;
		Method method = ((IProceduresOnError)kprogram).getGetterMethod();
		if (method != null) {
			try {
				obj = (ProceduresOnError)method.invoke(program, new Object[]{});
				if (obj==null) {
					obj = new ProceduresOnError();
					Field field = program.getClass().getDeclaredField(((IProceduresOnError)kprogram).getFieldName());
					field.setAccessible(true);
					field.set(program, obj);
				}
				obj.addProcedureInError(procedure, exception);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public synchronized void IProceduresOnError.addProcedureHandledInError(KProgram kprogram, Object program, Object procedure, Exception exception) {
		ProceduresOnError obj;

		this.isHandledErrors=true;
		Method method = ((IProceduresOnError)kprogram).getGetterMethod();
		if (method != null) {
			try {
				obj = (ProceduresOnError)method.invoke(program, new Object[]{});
				if (obj==null) {
					obj = new ProceduresOnError();
					Field field = program.getClass().getDeclaredField(((IProceduresOnError)kprogram).getFieldName());
					field.setAccessible(true);
					field.set(program, obj);
				}
				obj.addProcedureHandledInError(procedure, exception);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	public synchronized ProceduresOnError IProceduresOnError.getAllProceduresInError(Object program) {
		ProceduresOnError obj=null;
		if (this.getGetterMethod()!=null) {
			try {
				obj = (ProceduresOnError)this.getGetterMethod().invoke(program, new Object[]{});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return obj;
	}

	public boolean IProceduresOnError.isError() {
		return this.isErrors;
	}

	public boolean IProceduresOnError.isHandledError() {
		return this.isHandledErrors;
	}

    pointcut procsOnError(Object a): get(@OnError ProceduresOnError *.*) && this(a);

    before(Object procOnErrorField): procsOnError(procOnErrorField) {
    }

	/*
	 * Allow to get the procedures in error of a Process
	 */
	public static synchronized ProceduresOnError getProceduresInErrors(
			org.parallelj.mirror.Process process) {
		return ((IProceduresOnError)process.getProgram()).getAllProceduresInError(process.getContext());
	}

	// Interception around Runnable execution
	void around(Object self): call(void complete())
		&& target(org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall)
		&& this(self)  {

		RunnableProcedure.RunnableCall runnable = (RunnableProcedure.RunnableCall)thisJoinPoint.getTarget();
		RunnableProcedure procedure = (RunnableProcedure)runnable.getProcedure();
		if (runnable.getException() != null) {
			// There is an error !!!
			KProcess process = runnable.getProcess();
			if(procedure.getHandler() == null) {
				((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), runnable.getContext(), runnable.getException());
			} else {
				((IProceduresOnError)process.getProgram()).addProcedureHandledInError(process.getProgram(), process.getContext(), runnable.getContext(), runnable.getException());
			}
		}
		proceed(self);
	}

	// Interception around Callable execution
	void around(Object self): call(void complete())
		&& target(org.parallelj.internal.kernel.procedure.CallableProcedure.CallableCall)
		&& this(self)  {

		CallableProcedure.CallableCall callable = (CallableProcedure.CallableCall)thisJoinPoint.getTarget();
		CallableProcedure procedure = (CallableProcedure)callable.getProcedure();
		if (callable.getException() != null) {
			// There is an error !!!
			KProcess process = callable.getProcess();
			if(procedure.getHandler() == null) {
				((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), callable.getContext(), callable.getException());
			} else {
				((IProceduresOnError)process.getProgram()).addProcedureHandledInError(process.getProgram(), process.getContext(), callable.getContext(), callable.getException());			}
		}
		proceed(self);
	}

	boolean around(org.parallelj.internal.kernel.KJoin self, org.parallelj.internal.kernel.KProcess process)
		:execution(* org.parallelj.internal.kernel.KJoin+.isEnabled(..))
		&& this(self) && args(process)
		&& within(org.parallelj.internal.kernel.loop.KWhileLoop) {

		try {
			return proceed(self, process);
		} catch (Exception e) {
			((IProceduresOnError)process.getProgram()).addProcedureInError(process.getProgram(), process.getContext(), process.getContext(), e);
			MessageKind.E0005.format(process.getContext(), e);
			return false;
		}

	}

}


removed old renamed aspect

removed old renamed aspect

/**
 * palava - a java-php-bridge
 * Copyright (C) 2007-2010  CosmoCode GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package de.cosmocode.palava.jpa.aspectj;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

import de.cosmocode.palava.core.aop.AbstractPalavaAspect;
import de.cosmocode.palava.jpa.Transactional;

public final aspect TransactionAspect extends AbstractPalavaAspect issingleton() {

    private static final Logger LOG = LoggerFactory.getLogger(TransactionAspect.class);

    private Provider<EntityManager> currentManager;

    @Inject
    public void setProvider(Provider<EntityManager> manager) {
        this.currentManager = Preconditions.checkNotNull(manager, "Manager");
    }

    pointcut transactional(): execution(@Transactional * *.*(..));

    @SuppressAjWarnings("adviceDidNotMatch")
    Object around(): transactional() {
        final EntityManager manager = currentManager.get();
        LOG.trace("Retrieved entitymanager {}", manager);
        final EntityTransaction tx = manager.getTransaction();
        LOG.trace("Using transaction {}", tx);
        final boolean localTx = !tx.isActive();
        if (localTx) {
            LOG.trace("Beginning automatic transaction");
            tx.begin();
        } else {
            LOG.trace("Transaction already active");
        }

        final Object returnValue;

        try {
            returnValue = proceed();
        } catch (RuntimeException e) {
            LOG.error("Exception inside automatic transaction context", e);
            tx.rollback();
            throw e;
        }

        try {
            if (localTx && tx.isActive()) {
                tx.commit();
                LOG.trace("Committed automatic transaction");
            }
            return returnValue;
        } catch (PersistenceException e) {
            LOG.error("Commit in automatic transaction context failed", e);
            tx.rollback();
            throw e;
        }
    }

}


Adding support for $proc (continued)

Adding support for $proc (continued)

/**
 * Copyright (c) 2012 Tim Molderez.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the 3-Clause BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/BSD-3-Clause
 */

package be.ac.ua.ansymo.adbc.aspects;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.Vector;

import javax.script.ScriptException;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AdviceName;
import org.aspectj.lang.reflect.AdviceSignature;
import org.aspectj.lang.reflect.MethodSignature;

import be.ac.ua.ansymo.adbc.AdbcConfig;
import be.ac.ua.ansymo.adbc.annotations.advisedBy;
import be.ac.ua.ansymo.adbc.annotations.ensures;
import be.ac.ua.ansymo.adbc.annotations.invariant;
import be.ac.ua.ansymo.adbc.annotations.requires;
import be.ac.ua.ansymo.adbc.exceptions.InvariantException;
import be.ac.ua.ansymo.adbc.exceptions.PostConditionException;
import be.ac.ua.ansymo.adbc.exceptions.PreConditionException;
import be.ac.ua.ansymo.adbc.exceptions.SubstitutionException;
import be.ac.ua.ansymo.adbc.utilities.ContractInterpreter;

/**
 * This aspect enforces the contracts of all aspects in the application.
 * If a contract is broken, a ContractEnforcementException is thrown.
 *
 * @author Tim Molderez
 */
public aspect AspectContractEnforcer extends AbstractContractEnforcer {
	private JoinPoint tjp = null;	// The thisjoinpoint object of the user-advice
	private String advKind;			// User-advice kind (before, after around)

	// Contracts of the user-advice
	protected String[] advPre;
	protected String[] advPost;
	protected String[] advInv;

	private String[] advBySuffix = null;

	/**
	 * Contract enforcer for advice
	 */
	Object around(Object dyn): adviceexecution() && this(dyn)
	&& !within(be.ac.ua.ansymo.adbc.aspects.*) && excludeContractEnforcers() {
		/* Very sensitive pointcut! Do not perform any method calls here besides preCheck() and postCheck()
		 * or you'll trigger infinite pointcut matching! */

		// Retrieve the thisJoinPoint object of the user-advice
		try {
			/* Note that we're peeking into CallStack, not popping! Other contract enforcement
			 * still need this top entry! */
			tjp = CallStack.peek();
		} catch (EmptyStackException e) {
			/* In case the advice we've intercepted advises join points other than calls and executions,
			 * there's not going to be an entry on the CallStack for it..
			 * TODO: For now we don't support these kinds of advice yet.
			 * Still need to figure out some way to get access to the join point they advise.. */
			return proceed(dyn);
		}

		try {
			preCheck(thisJoinPoint, dyn);
			Object result = proceed(dyn);
			if (AdbcConfig.checkPostconditions) {
				postCheck(thisJoinPoint,dyn, result);
			}
			return result;
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate contract: " + e.getMessage());
		}
	}

	/*
	 * Do the necessary preparation and
	 * check all contracts before advice execution (preconditions, invariants, substitution principle)
	 */
	private void preCheck(JoinPoint jp, Object dyn) throws ScriptException {
		/* ****************************************************************
		 * Fetching the necessary info...
		 **************************************************************** */

		// Retrieve the join point advised by the user-advice
		tjp = CallStack.peek();

		// Retrieve the method being advised by the user-advice
		String m = tjp.getSignature().toString();
		int lastdot = m.lastIndexOf('.');
		m = m.substring(lastdot + 1, m.length());
		Method mBody = ((MethodSignature) tjp.getSignature()).getMethod();

		// Retrieve the user-advice
		AdviceSignature aSig = (AdviceSignature) (jp.getSignature());
		Method aBody = aSig.getAdvice();

		// Determine whether this advice is mentioned in an @advisedBy clause. And if so, which advice follow in that clause?
		boolean isAdvisedBy = isAdvisedBy(mBody, aBody);

		// Get the contracts of the user-advice's advised joinpoint
		// TODO: This currently does not work yet for higher-order advice..
		pre = new String[]{"true"};
		post = new String[]{"true"};
		inv = new String[]{"true"};

		if (mBody.isAnnotationPresent(requires.class)) {
			pre = mBody.getAnnotation(requires.class).value();
		}
		if (mBody.isAnnotationPresent(ensures.class)) {
			post = mBody.getAnnotation(ensures.class).value();
		}
		if (tjp.getTarget()!=null && tjp.getTarget().getClass().isAnnotationPresent(invariant.class)) {
			inv = tjp.getTarget().getClass().getAnnotation(invariant.class).value();
		}

		// Get the contracts of the user-advice itself
		advPre = new String[]{"true"};
		advPost = new String[]{"true"};
		advInv = new String[]{"true"};

		if (AdbcConfig.checkSubstitutionPrinciple || isAdvisedBy) {
			if (aBody.isAnnotationPresent(requires.class)) {
				advPre = aBody.getAnnotation(requires.class).value();
			}
			if (aBody.isAnnotationPresent(ensures.class)) {
				advPost = aBody.getAnnotation(ensures.class).value();
			}
			if (dyn.getClass().isAnnotationPresent(invariant.class)) {
				advInv = dyn.getClass().getAnnotation(invariant.class).value();
			}
		}

		// Determine user-advice kind (relying on its internal method name)
		advKind = "around";
		if (aSig.getName().contains("$before$")) {
			advKind = "before";
		} else if (aSig.getName().contains("$after$")) {
			advKind = "after";
		}

		/* ****************************************************************
		 * Binding contract variables
		 **************************************************************** */

		// Reset bindings
		ceval = new ContractInterpreter();

		// Resolve the $proc keyword
		if(isAdvisedBy && advKind.equals("around")) {
			pre = ceval.evalProc(advPre, pre);
			post = ceval.evalProc(advPost, post);

			Vector<String[]> advByPreSuffix = new Vector<String[]>();
			for (String adv : advBySuffix) {
				try {
					int separator = adv.lastIndexOf('.');
					String aspectName = adv.substring(0, separator);
					String adviceName = adv.substring(separator+1, adv.length());

					Class<?> asp = Class.forName(aspectName);

				} catch (ClassNotFoundException e) {e.printStackTrace();}
			}
		} else {
			pre = ceval.evalProc(advPre, pre);
			post = ceval.evalProc(advPost, post);
		}

		// Evaluate calls to the $old() function in postconditions of advice
		try {
			if (AdbcConfig.checkPostconditions) {
				ceval.setThisBinding(jp.getThis());
				ceval.setParameterBindings(aSig.getParameterNames(),jp.getArgs());
				advPost = ceval.evalOldFunction(advPost);
			}
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate old() call: " + e.getMessage());
		}

		// Bind parameter values of advised join point
		MethodSignature mSig = (MethodSignature) (tjp.getSignature());
		ceval.setParameterBindings(mSig.getParameterNames(),tjp.getArgs());

		/* ****************************************************************
		 * Actual contract enforcement
		 **************************************************************** */

		// Bind the "this" object
		ceval.setThisBinding(tjp.getTarget());

		// Evaluate calls to the $old() function in postconditions of the advised join point
		try {
			if (AdbcConfig.checkPostconditions) {
				post = ceval.evalOldFunction(post);
			}
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate old() call: " + e.getMessage());
		}

		// Test preconditions
		if (!advKind.equals("after")) {
			String stPreFailed = ceval.evalContract(pre);
			if (stPreFailed != null) {
				throw new PreConditionException(stPreFailed, getCaller());
			}
		}

		// Test invariants
		String invFailed = ceval.evalContract(inv);
		if (invFailed != null) {
			throw new InvariantException(invFailed, getCaller(), "precondition");
		}

		// Test advice substitution, if applicable
		if (!isAdvisedBy && AdbcConfig.checkSubstitutionPrinciple) {
			ceval.setThisBinding(jp.getThis());
			ceval.setParameterBindings(aSig.getParameterNames(),jp.getArgs());

			String jpPreFailed = ceval.evalContract(advPre);
			if (jpPreFailed != null) {
				throw new SubstitutionException(jpPreFailed, jp.getSignature().toString(), "precondition too strong");
			}

			invFailed = ceval.evalContract(advInv);
			if (invFailed != null) {
				throw new InvariantException(invFailed, jp.getSignature().toString(), "invariant not preserved");
			}
		}
	}

	/**
	 * Determine whether advice aBody appears in the advisedBy clause of method mBody (or the same method in an ancestor class)
	 * @param mBody
	 * @param aBody
	 * @return
	 */
	private boolean isAdvisedBy(Method mBody, Method aBody) {
		if (!aBody.isAnnotationPresent(AdviceName.class)) {
			return false;
		}

		String advName = aBody.getAnnotation(AdviceName.class).value();
		Class<?> mClass = mBody.getDeclaringClass();
		Class<?>[] mParTypes = mBody.getParameterTypes();
		String mName = mBody.getName();

		while(mClass!=null) {
			try {
				mBody = mClass.getMethod(mName, mParTypes);
				// If there's an @advisedBy annotation, go check whether aBody is mentioned..
				if (mBody.isAnnotationPresent(advisedBy.class)) {
					String[] advBy = mBody.getAnnotation(advisedBy.class).value();
					int i=0;
					for (String listedName : advBy) {
						if(listedName.equals(aBody.getDeclaringClass().getCanonicalName() + "." + advName)) {
							advBySuffix = Arrays.copyOfRange(advBy, i, advBy.length);
							return true;
						}
						i++;
					}
				}
			} catch (Exception e) {/* If the method is not found within mClass, do nothing.. */}

			mClass = mClass.getSuperclass();
		}
		return false;
	}

	/*
	 * Check contracts after advice execution (postconditions, invariants, substitution principle)
	 */
	private void postCheck(JoinPoint jp, Object dyn, Object result) throws ScriptException {
		// Bind the return value
		ceval.setReturnValueBinding(result);

		// Bind the this object of the advised join point
		ceval.setThisBinding(tjp.getTarget());

		// Test postconditions
		if (!advKind.equals("before")) {
			String stPostFailed = ceval.evalContract(post);
			if (stPostFailed != null) {
				throw new PostConditionException(stPostFailed, dyn.getClass()
						.toString());
			}
		}

		// Test invariants
		String invFailed = ceval.evalContract(inv);
		if (invFailed != null) {
			throw new InvariantException(invFailed, dyn.getClass().toString(), "postcondition");
		}

		// Test advice substitution
		if (AdbcConfig.checkSubstitutionPrinciple) {
			ceval.setThisBinding(jp.getThis());

			String jpPostFailed = ceval.evalContract(advPost);
			if (jpPostFailed != null) {
				throw new SubstitutionException(jpPostFailed, jp.getSignature()
						.toString(), "postcondition too weak");
			}

			invFailed = ceval.evalContract(advInv);
			if (invFailed != null) {
				throw new InvariantException(invFailed, tjp.getTarget().toString(), "invariant not preserved");
			}
		}
	}

	/*
	 * Retrieve the caller of the user-advice
	 */
	private String getCaller() {
		/* Runtime stack at this point:
		 * 0: getStackTrace()
		 * 1: getCaller()
		 * 2: preCheck()
		 * 3: preCheck()
		 * 4: around (contract enforcer)
		 * 5: user-advice
		 * 6: tjp
		 * 7: caller <== This is what we're interested in..	*/
		StackTraceElement elem = Thread.currentThread().getStackTrace()[7];
		return elem.toString();
	}
}

/**
 * Copyright (c) 2012 Tim Molderez.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the 3-Clause BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/BSD-3-Clause
 */

package be.ac.ua.ansymo.adbc.aspects;

import java.lang.reflect.Method;
import java.util.EmptyStackException;

import javax.script.ScriptException;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AdviceName;
import org.aspectj.lang.reflect.AdviceSignature;
import org.aspectj.lang.reflect.MethodSignature;

import be.ac.ua.ansymo.adbc.AdbcConfig;
import be.ac.ua.ansymo.adbc.annotations.advisedBy;
import be.ac.ua.ansymo.adbc.annotations.ensures;
import be.ac.ua.ansymo.adbc.annotations.invariant;
import be.ac.ua.ansymo.adbc.annotations.requires;
import be.ac.ua.ansymo.adbc.exceptions.InvariantException;
import be.ac.ua.ansymo.adbc.exceptions.PostConditionException;
import be.ac.ua.ansymo.adbc.exceptions.PreConditionException;
import be.ac.ua.ansymo.adbc.exceptions.SubstitutionException;
import be.ac.ua.ansymo.adbc.utilities.ContractInterpreter;

/**
 * This aspect enforces the contracts of all aspects in the application.
 * If a contract is broken, a ContractEnforcementException is thrown.
 *
 * @author Tim Molderez
 */
public aspect AspectContractEnforcer extends AbstractContractEnforcer {
	private JoinPoint tjp = null;	// The thisjoinpoint object of the user-advice
	private String advKind;			// User-advice kind (before, after around)

	// Contracts of the user-advice
	protected String[] advPre;
	protected String[] advPost;
	protected String[] advInv;

	/**
	 * Contract enforcer for advice
	 */
	Object around(Object dyn): adviceexecution() && this(dyn)
	&& !within(be.ac.ua.ansymo.adbc.aspects.*) && excludeContractEnforcers() {
		/* Very sensitive pointcut! Do not perform any method calls here besides preCheck() and postCheck()
		 * or you'll trigger infinite pointcut matching! */

		// Retrieve the thisJoinPoint object of the user-advice
		try {
			/* Note that we're peeking into CallStack, not popping! Other contract enforcement
			 * still need this top entry! */
			tjp = CallStack.peek();
		} catch (EmptyStackException e) {
			/* In case the advice we've intercepted advises join points other than calls and executions,
			 * there's not going to be an entry on the CallStack for it..
			 * TODO: For now we don't support these kinds of advice yet.
			 * Still need to figure out some way to get access to the join point they advise.. */
			return proceed(dyn);
		}

		try {
			preCheck(thisJoinPoint, dyn);
			Object result = proceed(dyn);
			if (AdbcConfig.checkPostconditions) {
				postCheck(thisJoinPoint,dyn, result);
			}
			return result;
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate contract: " + e.getMessage());
		}
	}

	/*
	 * Do the necessary preparation and
	 * check all contracts before advice execution (preconditions, invariants, substitution principle)
	 */
	private void preCheck(JoinPoint jp, Object dyn) throws ScriptException {
		/* ****************************************************************
		 * Fetching the necessary info...
		 **************************************************************** */

		// Retrieve the join point advised by the user-advice
		tjp = CallStack.peek();

		// Retrieve the method being advised by the user-advice
		String m = tjp.getSignature().toString();
		int lastdot = m.lastIndexOf('.');
		m = m.substring(lastdot + 1, m.length());
		Method mBody = ((MethodSignature) tjp.getSignature()).getMethod();

		// Retrieve the user-advice
		AdviceSignature aSig = (AdviceSignature) (jp.getSignature());
		Method aBody = aSig.getAdvice();

		// Determine whether this advice is mentioned in an @advisedBy clause
		boolean isAdvisedBy = isAdvisedBy(mBody, aBody);

		// Get the contracts of the user-advice's advised joinpoint
		// TODO: This currently does not work yet for higher-order advice..
		pre = new String[]{"true"};
		post = new String[]{"true"};
		inv = new String[]{"true"};

		if (mBody.isAnnotationPresent(requires.class)) {
			pre = mBody.getAnnotation(requires.class).value();
		}
		if (mBody.isAnnotationPresent(ensures.class)) {
			post = mBody.getAnnotation(ensures.class).value();
		}
		if (tjp.getTarget()!=null && tjp.getTarget().getClass().isAnnotationPresent(invariant.class)) {
			inv = tjp.getTarget().getClass().getAnnotation(invariant.class).value();
		}

		// Get the contracts of the user-advice itself
		advPre = new String[]{"true"};
		advPost = new String[]{"true"};
		advInv = new String[]{"true"};

		if (AdbcConfig.checkSubstitutionPrinciple || isAdvisedBy) {
			if (aBody.isAnnotationPresent(requires.class)) {
				advPre = aBody.getAnnotation(requires.class).value();
			}
			if (aBody.isAnnotationPresent(ensures.class)) {
				advPost = aBody.getAnnotation(ensures.class).value();
			}
			if (dyn.getClass().isAnnotationPresent(invariant.class)) {
				advInv = dyn.getClass().getAnnotation(invariant.class).value();
			}
		}

		// Determine user-advice kind (relying on its internal method name)
		advKind = "around";
		if (aSig.getName().contains("$before$")) {
			advKind = "before";
		} else if (aSig.getName().contains("$after$")) {
			advKind = "after";
		}

		/* ****************************************************************
		 * Binding contract variables
		 **************************************************************** */

		// Reset bindings
		ceval = new ContractInterpreter();

		// Resolve the $proc keyword
		if(isAdvisedBy && advKind.equals("around")) {
			pre = ceval.evalProc(advPre, pre);
			post = ceval.evalProc(advPost, post);
		} else {

		}

		// Evaluate calls to the $old() function in postconditions of advice
		try {
			if (AdbcConfig.checkPostconditions) {
				ceval.setThisBinding(jp.getThis());
				ceval.setParameterBindings(aSig.getParameterNames(),jp.getArgs());
				advPost = ceval.evalOldFunction(advPost);
			}
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate old() call: " + e.getMessage());
		}

		// Bind parameter values of advised join point
		MethodSignature mSig = (MethodSignature) (tjp.getSignature());
		ceval.setParameterBindings(mSig.getParameterNames(),tjp.getArgs());

		/* ****************************************************************
		 * Actual contract enforcement
		 **************************************************************** */

		// Bind the "this" object
		ceval.setThisBinding(tjp.getTarget());

		// Evaluate calls to the $old() function in postconditions of the advised join point
		try {
			if (AdbcConfig.checkPostconditions) {
				post = ceval.evalOldFunction(post);
			}
		} catch (ScriptException e) {
			throw new RuntimeException("Failed to evaluate old() call: " + e.getMessage());
		}

		// Test preconditions
		if (!advKind.equals("after")) {
			String stPreFailed = ceval.evalContract(pre);
			if (stPreFailed != null) {
				throw new PreConditionException(stPreFailed, getCaller());
			}
		}

		// Test invariants
		String invFailed = ceval.evalContract(inv);
		if (invFailed != null) {
			throw new InvariantException(invFailed, getCaller(), "precondition");
		}

		// Test advice substitution, if applicable
		if (!isAdvisedBy && AdbcConfig.checkSubstitutionPrinciple) {
			ceval.setThisBinding(jp.getThis());
			ceval.setParameterBindings(aSig.getParameterNames(),jp.getArgs());

			String jpPreFailed = ceval.evalContract(advPre);
			if (jpPreFailed != null) {
				throw new SubstitutionException(jpPreFailed, jp.getSignature().toString(), "precondition too strong");
			}

			invFailed = ceval.evalContract(advInv);
			if (invFailed != null) {
				throw new InvariantException(invFailed, jp.getSignature().toString(), "invariant not preserved");
			}
		}
	}

	/**
	 * Determine whether advice aBody appears in the advisedBy clause of method mBody (or the same method in an ancestor class)
	 * @param mBody
	 * @param aBody
	 * @return
	 */
	private boolean isAdvisedBy(Method mBody, Method aBody) {
		if (!aBody.isAnnotationPresent(AdviceName.class)) {
			return false;
		}

		String advName = aBody.getAnnotation(AdviceName.class).value();
//		System.out.println(advName + mBody);
		Class<?> mClass = mBody.getDeclaringClass();
		Class<?>[] mParTypes = mBody.getParameterTypes();
		String mName = mBody.getName();

		while(mClass!=null) {
			try {
				mBody = mClass.getMethod(mName, mParTypes);
				// If there's an @advisedBy annotation, go check whether aBody is mentioned..
				if (mBody.isAnnotationPresent(advisedBy.class)) {
					String[] advBy = mBody.getAnnotation(advisedBy.class).value();
					for (String listedName : advBy) {
						if(listedName.equals(aBody.getDeclaringClass().getCanonicalName() + "." + advName)) {
							return true;
						}
					}
				}
			} catch (Exception e) {/* If the method is not found within mClass, do nothing.. */}

			mClass = mClass.getSuperclass();
		}
		return false;
	}

	/*
	 * Check contracts after advice execution (postconditions, invariants, substitution principle)
	 */
	private void postCheck(JoinPoint jp, Object dyn, Object result) throws ScriptException {
		// Bind the return value
		ceval.setReturnValueBinding(result);

		// Bind the this object of the advised join point
		ceval.setThisBinding(tjp.getTarget());

		// Test postconditions
		if (!advKind.equals("before")) {
			String stPostFailed = ceval.evalContract(post);
			if (stPostFailed != null) {
				throw new PostConditionException(stPostFailed, dyn.getClass()
						.toString());
			}
		}

		// Test invariants
		String invFailed = ceval.evalContract(inv);
		if (invFailed != null) {
			throw new InvariantException(invFailed, dyn.getClass().toString(), "postcondition");
		}

		// Test advice substitution
		if (AdbcConfig.checkSubstitutionPrinciple) {
			ceval.setThisBinding(jp.getThis());

			String jpPostFailed = ceval.evalContract(advPost);
			if (jpPostFailed != null) {
				throw new SubstitutionException(jpPostFailed, jp.getSignature()
						.toString(), "postcondition too weak");
			}

			invFailed = ceval.evalContract(advInv);
			if (invFailed != null) {
				throw new InvariantException(invFailed, tjp.getTarget().toString(), "invariant not preserved");
			}
		}
	}

	/*
	 * Retrieve the caller of the user-advice
	 */
	private String getCaller() {
		/* Runtime stack at this point:
		 * 0: getStackTrace()
		 * 1: getCaller()
		 * 2: preCheck()
		 * 3: preCheck()
		 * 4: around (contract enforcer)
		 * 5: user-advice
		 * 6: tjp
		 * 7: caller <== This is what we're interested in..	*/
		StackTraceElement elem = Thread.currentThread().getStackTrace()[7];
		return elem.toString();
	}
}