br.gov.frameworkdemoiselle.internal.interceptor.AuditableInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for br.gov.frameworkdemoiselle.internal.interceptor.AuditableInterceptor.java

Source

/*
 * Demoiselle Framework
 * Copyright (C) 2010 SERPRO
 * ----------------------------------------------------------------------------
 * This file is part of Demoiselle Framework.
 * 
 * Demoiselle Framework is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation.
 * 
 * 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 Lesser General Public License version 3
 * along with this program; if not,  see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA  02110-1301, USA.
 * ----------------------------------------------------------------------------
 * Este arquivo  parte do Framework Demoiselle.
 * 
 * O Framework Demoiselle  um software livre; voc pode redistribu-lo e/ou
 * modific-lo dentro dos termos da GNU LGPL verso 3 como publicada pela Fundao
 * do Software Livre (FSF).
 * 
 * Este programa  distribudo na esperana que possa ser til, mas SEM NENHUMA
 * GARANTIA; sem uma garantia implcita de ADEQUAO a qualquer MERCADO ou
 * APLICAO EM PARTICULAR. Veja a Licena Pblica Geral GNU/LGPL em portugus
 * para maiores detalhes.
 * 
 * Voc deve ter recebido uma cpia da GNU LGPL verso 3, sob o ttulo
 * "LICENCA.txt", junto com esse programa. Se no, acesse <http://www.gnu.org/licenses/>
 * ou escreva para a Fundao do Software Livre (FSF) Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA.
 */
package br.gov.frameworkdemoiselle.internal.interceptor;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;

import br.gov.frameworkdemoiselle.DemoiselleException;
import br.gov.frameworkdemoiselle.annotation.Name;
import br.gov.frameworkdemoiselle.internal.bootstrap.CoreBootstrap;
import br.gov.frameworkdemoiselle.internal.implementation.CoreBundle;
import br.gov.frameworkdemoiselle.internal.implementation.DefaultAuditInfo;
import br.gov.frameworkdemoiselle.internal.implementation.DefaultAuditor;
import br.gov.frameworkdemoiselle.security.AuditInfo;
import br.gov.frameworkdemoiselle.security.Auditable;
import br.gov.frameworkdemoiselle.security.Auditor;
import br.gov.frameworkdemoiselle.security.RequiredPermission;
import br.gov.frameworkdemoiselle.security.SecurityContext;
import br.gov.frameworkdemoiselle.security.User;
import br.gov.frameworkdemoiselle.util.ResourceBundle;
import br.gov.frameworkdemoiselle.util.Strings;

/**
 * This is the default interceptor for Auditable elements. This interceptor will
 * look for registered implementations for {@link Auditor} interface. For each
 * registered auditor, an {@link AuditInfo} implementation will be looked up,
 * and if a match is found than the auditor's {@link Auditor#audit(AuditInfo)}
 * method will be called.
 * 
 * @author SERPRO
 * 
 */
@Interceptor
@Auditable
public class AuditableInterceptor {

    private final ResourceBundle bundle;

    private final Logger log;

    private final Instance<Auditor> auditorInstances;

    private final Instance<AuditInfo> auditInfoInstances;

    private final Instance<SecurityContext> securityContext;

    @Inject
    public AuditableInterceptor(Instance<Auditor> auditor, Instance<AuditInfo> auditInfoInstances,
            Instance<SecurityContext> securityContext, Logger log,
            @Name("demoiselle-core-bundle") ResourceBundle bundle) {
        this.log = log;
        this.bundle = bundle;
        this.auditorInstances = auditor;
        this.auditInfoInstances = auditInfoInstances;
        this.securityContext = securityContext;
    }

    @AroundInvoke
    public Object audit(InvocationContext ic) throws Exception {
        // TODO: Use the bundle to log information
        String operation = getOperation(ic);
        if (log.isDebugEnabled()) {
            log.debug("Starting auditing on operation : " + operation);
        }
        // TODO: This has to be parameterized with some form
        if ("getDelegate".equals(operation)) {
            return ic.proceed();
        }
        if (!auditorInstances.isUnsatisfied()) {
            try {
                throwExceptionIfHasOnlyDefaultAuditorInstances();
                throwExceptionIfHasOnlyDefaultAuditInfoInstances();
                for (Iterator<Auditor> it = auditorInstances.iterator(); it.hasNext();) {
                    Auditor internalAuditor = it.next();
                    Class<?> internalAuditorClass = internalAuditor.getClass();
                    // Proxy classes are skiped. The correct implementation is
                    // used.
                    if (isAuditorAProxy(internalAuditorClass)) {
                        internalAuditorClass = (Class<?>) internalAuditorClass.getSuperclass();
                    }
                    if (isAuditorAllowed(internalAuditorClass)) {
                        // Other Auditor instances exists or were already
                        // processed.
                        continue;
                    }
                    String auditorClass = internalAuditorClass.getName();
                    Class<?> auditorClassTypeParameter = getAuditorTargetResource(internalAuditorClass);
                    if (log.isTraceEnabled()) {
                        log.trace("--------------------------------------------");
                        log.trace("Current InternalAuditor instance: " + auditorClass);
                        log.trace("Current AuditorClassTypeParameter: " + auditorClassTypeParameter);
                        log.trace("--------------------------------------------");
                    }
                    for (Iterator<AuditInfo> ita = auditInfoInstances.iterator(); ita.hasNext();) {
                        AuditInfo auditInfo = ita.next();
                        Class<?> auditInfoClass = auditInfo.getClass();
                        if (DefaultAuditInfo.class.isAssignableFrom(auditInfoClass)) {
                            continue;
                        }
                        Class<?> auditInfoClassTypeParameter = getAuditorTargetResource(auditInfoClass);
                        if (log.isTraceEnabled()) {
                            log.trace("--------------------------------------------");
                            log.trace("Current AuditInfoClass: " + auditInfoClass);
                            log.trace("Current AuditInfoClassTypeParameter: " + auditInfoClassTypeParameter);
                            log.trace("--------------------------------------------");
                        }
                        // We only process audit info instance whose type
                        // parameter matches the
                        // type parameter for the auditor.
                        if (auditorClassTypeParameter == auditInfoClassTypeParameter) {
                            log.debug("auditorClassTypeParameter == auditInfoClassTypeParameter: "
                                    + auditorClassTypeParameter + " : " + auditInfoClassTypeParameter);
                            log.debug("TypeParameters Match. Processing auditInfo instance:" + auditInfo + " ...");
                            processAuditInfo(ic, operation, internalAuditor, auditorClass, auditInfo);
                        } else {
                            // Do not process this audit info for the current
                            // auditor.
                            log.debug("TypeParameters doen't match. Skiping this auditInfo (" + auditInfoClass
                                    + ") for this auditor (" + auditorClass + ")");
                            continue;
                        }
                    }
                    log.debug("All AuditInfo processed for auditor: " + auditorClass);
                }
            } catch (DemoiselleException ex) {
                // TODO: Move message to bundle
                log.error("Instance of : " + getType(ic)
                        + " is annotated with Auditable but without correct configuration!");
                log.error(ex.toString(), ex);
                throw ex;
            } catch (Exception ex) {
                log.error(ex.toString(), ex);
                throw ex;
            }
        } else {
            log.warn("No instances of br.gov.frameworkdemoiselle.security.Auditor found on classpath!");
        }
        return ic.proceed();
    }

    private void throwExceptionIfHasOnlyDefaultAuditInfoInstances() {
        if (hasOnlyDefaultAuditInfoInstances(auditInfoInstances)) {
            throw new DemoiselleException(
                    CoreBundle.get().getString("audit-info-not-defined", Auditor.class.getSimpleName()));
        }
    }

    private void throwExceptionIfHasOnlyDefaultAuditorInstances() {
        if (hasOnlyDefaultAuditorInstances(auditorInstances)) {
            throw new DemoiselleException(
                    CoreBundle.get().getString("auditor-not-defined", Auditor.class.getSimpleName()));
        }
    }

    /**
     * @param internalAuditorClass
     * @return
     * @throws Exception
     */
    private Class<?> getAuditorTargetResource(Class<?> internalAuditorClass) throws Exception {
        Class<?> auditorClassTypeParameter = getFirstInterfaceTypeParameter(internalAuditorClass);
        return auditorClassTypeParameter;
    }

    /**
     * @param internalAuditorClass
     * @return
     */
    private boolean isAuditorAProxy(Class<?> internalAuditorClass) {
        return internalAuditorClass.getName().toLowerCase(Locale.ENGLISH).contains("$proxy$");
    }

    /**
     * @param internalAuditorClass
     * @return
     */
    private boolean isAuditorAllowed(Class<?> internalAuditorClass) {
        return DefaultAuditor.class.isAssignableFrom(internalAuditorClass);
    }

    private boolean hasOnlyDefaultAuditInfoInstances(Instance<AuditInfo> auditInfoInstances) {
        for (Iterator<AuditInfo> it = auditInfoInstances.iterator(); it.hasNext();) {
            AuditInfo internalAuditInfo = it.next();
            if (!DefaultAuditInfo.class.isAssignableFrom(internalAuditInfo.getClass())) {
                return false;
            }
        }
        return true;
    }

    private Class<?> getFirstInterfaceTypeParameter(Class<?> internalAuditorClass) throws Exception {
        Type genericInterface = internalAuditorClass.getGenericInterfaces()[0];
        if (genericInterface != null) {
            String gStr = genericInterface.toString();
            String strType = StringUtils.EMPTY;
            if (gStr.indexOf('<') != -1) {
                strType = gStr.substring(gStr.indexOf('<') + 1, gStr.indexOf('>'));
            }
            if (StringUtils.isNotEmpty(strType)) {
                return Class.forName(strType);
            }
        }
        return null;
    }

    private boolean hasOnlyDefaultAuditorInstances(Instance<Auditor> auditorInstances) {
        for (Iterator<Auditor> it = auditorInstances.iterator(); it.hasNext();) {
            Auditor internalAuditor = it.next();
            if (!DefaultAuditor.class.isAssignableFrom(internalAuditor.getClass())) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param ic
     * @param operation
     * @param internalAuditor
     * @param auditorClass
     * @param auditInfo
     * @throws Exception
     */
    private void processAuditInfo(InvocationContext ic, String operation, Auditor internalAuditor,
            String auditorClass, AuditInfo auditInfo) throws Exception {
        setAuditInfoProperties(ic, operation, auditInfo);
        try {
            // Asking the auditorInstances to implement de audit operation
            internalAuditor.audit(auditInfo);
        } catch (RuntimeException ex) {
            log.error(ex.toString(), ex);
            throw ex;
        }
    }

    /**
     * @param ic
     * @param operation
     * @param auditInfo
     * @throws Exception
     */
    private void setAuditInfoProperties(InvocationContext ic, String operation, AuditInfo<?> auditInfo)
            throws Exception {
        if (auditInfo != null) {
            auditInfo.setOperation(operation);
            auditInfo.setParameters(ic.getParameters());
            auditInfo.setTarget(ic.getTarget());
            auditInfo.setTargetType(getType(ic));
            // TODO: Should we use the br.gov.frameworkdemoiselle.security.User
            // interface?
            auditInfo.setUser(getUsername());
            auditInfo.setResource(getResource(ic));
            auditInfo.setDateTime(new Date());
        }
    }

    /**
     * Returns the id of the currently logged in user.
     * 
     * @return the id of the currently logged in user
     */
    private String getUsername() {
        String username = "";
        if (securityContext.isUnsatisfied()) {
            log.warn("No instances of br.gov.frameworkdemoiselle.security.SecurityContext found on classpath!");
            return username;
        }
        User user = securityContext.get().getUser();

        if (user != null && user.getId() != null) {
            username = user.getId();
        }

        return username;
    }

    /**
     * Returns the resource defined in {@code @Auditable} annotation, the name
     * defined in {@code @Name} annotation or the class name itself
     * 
     * @param ic
     *            the {@code InvocationContext} in which the method is being
     *            called
     * @return the resource defined in {@code @RequiredPermission} annotation,
     *         the name defined in {@code @Name} annotation or the class name
     *         itself
     */
    private String getResource(InvocationContext ic) {
        Auditable auditable = ic.getMethod().getAnnotation(Auditable.class);

        if (auditable == null || Strings.isEmpty(auditable.resource())) {
            if (ic.getTarget().getClass().getAnnotation(Name.class) == null) {
                // Returning resource Target.class.simpleName...;
                return getType(ic).getName();
            } else {
                // Returning resource Target.class.annotation(Name).value...;
                return ic.getTarget().getClass().getAnnotation(Name.class).value();
            }
        } else {
            // Returning annotation defined resource...
            return auditable.resource();
        }
    }

    private String getOperation(InvocationContext ic) {
        Auditable auditable = ic.getMethod().getAnnotation(Auditable.class);

        if (auditable == null || Strings.isEmpty(auditable.resource())) {
            if (ic.getMethod().getAnnotation(Name.class) == null) {
                return ic.getMethod().getName();
            } else {
                return ic.getMethod().getAnnotation(Name.class).value();
            }
        } else {
            return auditable.operation();
        }
    }

    private final Class<?> getType(final InvocationContext ic) {
        Class<?> type = ic.getTarget().getClass();
        type = getTargetType(type);
        return type;
    }

    public static Class<?> getTargetType(Class<?> type) {
        if (!CoreBootstrap.isAnnotatedType(type)) {
            type = type.getSuperclass();
        }
        return type;
    }

}