nl.strohalm.cyclos.aop.TraceAspect.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.aop.TraceAspect.java

Source

/*
 This file is part of Cyclos.
    
 Cyclos 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.
    
 Cyclos 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 Cyclos; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.aop;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.fetch.FetchService;
import nl.strohalm.cyclos.services.permissions.PermissionService;
import nl.strohalm.cyclos.utils.ClassHelper;
import nl.strohalm.cyclos.utils.CurrentInvocationData;
import nl.strohalm.cyclos.utils.CurrentTransactionData;
import nl.strohalm.cyclos.utils.access.AdminAction;
import nl.strohalm.cyclos.utils.access.BrokerAction;
import nl.strohalm.cyclos.utils.access.DontEnforcePermission;
import nl.strohalm.cyclos.utils.access.IgnoreMember;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.access.MemberAction;
import nl.strohalm.cyclos.utils.access.OperatorAction;
import nl.strohalm.cyclos.utils.access.PathToMember;
import nl.strohalm.cyclos.utils.access.Permission;
import nl.strohalm.cyclos.utils.access.PermissionCheck;
import nl.strohalm.cyclos.utils.access.RelatedEntity;
import nl.strohalm.cyclos.utils.access.ServicePermissionsDescriptor;
import nl.strohalm.cyclos.utils.access.SystemAction;
import nl.strohalm.cyclos.utils.logging.LoggingHandler;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * AOP aspect used to log action execution
 * @author luis
 */
@Aspect
public class TraceAspect {

    private FetchService fetchService;
    private PermissionService permissionService;
    private final Map<Method, ServicePermissionsDescriptor> cachedDescriptors = new HashMap<Method, ServicePermissionsDescriptor>();
    private LoggingHandler loggingHandler;
    private ThreadLocal<Boolean> executing;

    public TraceAspect() {
        executing = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return Boolean.FALSE;
            }
        };
    }

    public void setFetchService(final FetchService fetchService) {
        this.fetchService = fetchService;
    }

    public void setLoggingHandler(final LoggingHandler loggingHandler) {
        this.loggingHandler = loggingHandler;
    }

    public void setPermissionService(final PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    /**
     * Trace this method execution, checking permission and generating logs
     */
    @Around("target(nl.strohalm.cyclos.services.Service)")
    public Object trace(final ProceedingJoinPoint joinPoint) throws Throwable {
        final boolean alreadyExecuting = executing.get();
        if (!alreadyExecuting) {
            // If not already executing, do the trace
            executing.set(true);
            try {
                // Trace this execution
                return doTrace(joinPoint);
            } catch (final Throwable t) {
                // Store the current exception
                CurrentTransactionData.setError(t);
                throw t;
            } finally {
                // We cannot forget to clear the thread local!
                executing.remove();
            }
        } else {
            // If already executing, just proceed normally
            return joinPoint.proceed();
        }
    }

    /**
     * Returns an permission descriptor instance for the given method
     */
    private ServicePermissionsDescriptor buildPermissionDescriptor(final Method method) {
        final ServicePermissionsDescriptor descriptor = new ServicePermissionsDescriptor();
        descriptor.setMethod(method);

        final DontEnforcePermission dontEnforcePermission = ClassHelper.findAnnotation(method,
                DontEnforcePermission.class, true);
        if (dontEnforcePermission != null) {
            descriptor.setSkipPermissionCheck(true);
            descriptor.setTraceableAction(dontEnforcePermission.traceable());
            return descriptor;
        } else {
            final SystemAction sysAction = ClassHelper.findAnnotation(method, SystemAction.class);
            final AdminAction adminAction = ClassHelper.findAnnotation(method, AdminAction.class);
            final MemberAction memberAction = ClassHelper.findAnnotation(method, MemberAction.class);
            final BrokerAction brokerAction = ClassHelper.findAnnotation(method, BrokerAction.class);
            final OperatorAction operatorAction = ClassHelper.findAnnotation(method, OperatorAction.class);

            // Find member-related annotations on method, and, if not found, on class
            final RelatedEntity relatedEntity = ClassHelper.findAnnotation(method, RelatedEntity.class, true);
            final PathToMember pathToMember = ClassHelper.findAnnotation(method, PathToMember.class, true);
            final IgnoreMember ignoreMember = ClassHelper.findAnnotation(method, IgnoreMember.class);

            // Build the descriptor
            descriptor.setSystemAction(sysAction != null);
            descriptor.setPermissionService(permissionService);
            descriptor.setFetchService(fetchService);
            descriptor.setAnnotations(adminAction, memberAction, brokerAction, operatorAction);
            descriptor.setRelatedEntity(relatedEntity);
            descriptor.setPathToMember(pathToMember);
            descriptor.setIgnoreMember(ignoreMember != null);

            return descriptor;
        }
    }

    /**
     * Execute the trace
     */
    private Object doTrace(final ProceedingJoinPoint joinPoint) throws Throwable {
        // Retrieve the method reference
        final Signature signature = joinPoint.getSignature();
        final Method method = ((MethodSignature) signature).getMethod();

        // Retrieve the permission descriptor for that method
        final ServicePermissionsDescriptor descriptor = getPermissionDescriptor(method);

        if (descriptor.isSkipPermissionCheck()
                || (descriptor.isForSystem() && CurrentInvocationData.isSystemInvocation())) {
            // only log client invocations not annotated with the DontEnforcePermission annotation or marked as traceable.
            if ((!descriptor.isSkipPermissionCheck() || descriptor.isTraceableAction())
                    && !CurrentInvocationData.isSystemInvocation()) {
                return executeAndLogAction(null, joinPoint);
            } else {
                // This is not an action, proceed normally
                return joinPoint.proceed();
            }
        } else if (descriptor.isOnlyForSystem() && !CurrentInvocationData.isSystemInvocation()) {
            throw new PermissionDeniedException();
        } else if (!descriptor.isAnnotated()) {
            throw new IllegalArgumentException("The method '" + method
                    + "' is not secured correctly. It must be annotated using some of the security annotations.");
        } else {
            // This is an action - verify related member permission
            final Object[] args = joinPoint.getArgs();
            final PermissionCheck check = descriptor.checkPermission(args.length == 0 ? null : args[0]);
            if (!check.isGranted()) {
                // Determine if log is being generated
                final boolean generateLog = loggingHandler.isTraceEnabled() && LoggedUser.isValid();

                if (generateLog) {
                    loggingHandler.logPermissionDenied(LoggedUser.user(), method, args);
                }
                throw new PermissionDeniedException();
            }

            // Log the action execution
            return executeAndLogAction(check, joinPoint);
        }
    }

    private Object executeAndLogAction(final PermissionCheck check, final ProceedingJoinPoint joinPoint)
            throws Throwable {
        Object[] args = null;
        Signature signature = null;
        Method method = null;
        Permission permission = null;

        // Determine if log is being generated
        final boolean generateLog = loggingHandler.isTraceEnabled() && LoggedUser.isValid();

        if (generateLog) {
            args = joinPoint.getArgs();
            signature = joinPoint.getSignature();
            method = ((MethodSignature) signature).getMethod();
            permission = check == null ? null : check.getPermission();
        }
        try {
            final Object retVal = joinPoint.proceed();
            if (generateLog) {
                loggingHandler.trace(LoggedUser.remoteAddress(), LoggedUser.user(), permission, method, args,
                        retVal);
            }
            return retVal;
        } catch (final Throwable t) {
            if (generateLog) {
                loggingHandler.traceError(LoggedUser.user(), permission, method, args, t);
            }
            throw t;
        }
    }

    /**
     * Returns the cached permission descriptor for this method, or creates a new one on a cache miss
     */
    private ServicePermissionsDescriptor getPermissionDescriptor(final Method method) {
        ServicePermissionsDescriptor descriptor = cachedDescriptors.get(method);
        if (descriptor == null) {
            descriptor = buildPermissionDescriptor(method);
            cachedDescriptors.put(method, descriptor);
        }
        return descriptor;
    }

}