edu.utah.further.ds.impl.advice.QpMonitorAdvice.java Source code

Java tutorial

Introduction

Here is the source code for edu.utah.further.ds.impl.advice.QpMonitorAdvice.java

Source

/**
 * Copyright (C) [2013] [The FURTHeR Project]
 *
 * 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 edu.utah.further.ds.impl.advice;

import static edu.utah.further.core.api.time.TimeUtil.ONE_SECOND;
import static edu.utah.further.ds.api.util.AttributeName.META_DATA;
import static edu.utah.further.ds.api.util.AttributeName.QUERY_CONTEXT;

import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;

import edu.utah.further.core.api.chain.ChainRequest;
import edu.utah.further.core.api.chain.RequestProcessor;
import edu.utah.further.core.api.constant.Strings;
import edu.utah.further.core.api.exception.ApplicationException;
import edu.utah.further.core.api.lang.ReflectionUtil;
import edu.utah.further.core.api.text.StringUtil;
import edu.utah.further.core.chain.UtilityProcessor;
import edu.utah.further.ds.api.util.StatusType;
import edu.utah.further.fqe.ds.api.domain.DsMetaData;
import edu.utah.further.fqe.ds.api.domain.QueryContext;
import edu.utah.further.fqe.ds.api.domain.ResultContext;
import edu.utah.further.fqe.ds.api.domain.StatusMetaData;

/**
 * Query Processor Monitor Advice is like a watch guard: it reports status of each
 * {@link UtilityProcessor} as well as handles when a {@link UtilityProcessor} fails by
 * terminating the chain. This implementation does NOT wire status processors into the
 * request chain, but invokes them directly from this class.
 * <p>
 * Currently the FURTHeR project uses Spring-AOP for its AOP implementation which is a
 * purley java based AOP implementation using dynamic proxies. In order for the following
 * aspect to advise a QueryProcessor, the processor <i>must</i> be wired as a
 * Spring bean and be available in the {@link ApplicationContext}.
 * <p>
 * Also note that due to Spring's nature of implementation, one can not advise a self
 * invoked method, that is, a method which is called from another method in the same
 * class. Spring would instead create a proxy around the method which calls the method you
 * wanted advised and your method will not be advised.
 * <p>
 * Additionally, an advised processor cannot be final.
 * <p>
 * Monitor aspect does not apply to all life cycles and should only be used on life
 * cycle's which operates with a {@link QueryContext}.
 * <p>
 * This aspect relies on status reporter dependencies. Think of it as an advice template
 * whose status notification logic is executed by function pointers. These can be wired to
 * different implementations in main/test spring contexts, or even injected from another
 * OSGi module inside an ESB.
 * -----------------------------------------------------------------------------------<br>
 * (c) 2008-2013 FURTHeR Project, Health Sciences IT, University of Utah<br>
 * Contact: {@code <further@utah.edu>}<br>
 * Biomedical Informatics, 26 South 2000 East<br>
 * Room 5775 HSEB, Salt Lake City, UT 84112<br>
 * Day Phone: 1-801-581-4080<br>
 * -----------------------------------------------------------------------------------
 * 
 * @author N. Dustin Schultz {@code <dustin.schultz@utah.edu>}
 * @version Feb 3, 2010
 */
@Component("qpMonitorAdvice")
public final class QpMonitorAdvice extends AbstractQpMonitorAdvice {
    // ========================= CONSTANTS =================================

    /**
     * A logger that helps identify this class' printouts.
     */
    private static final Logger log = LoggerFactory.getLogger(QpMonitorAdvice.class);

    // ========================= FIELDS ====================================

    // ========================= ADVICES ===================================

    /*
     * (non-Javadoc)
     * 
     * @seef
     * edu.utah.further.ds.api.advice.RequestProcessorAroundAdvice#doAround(org.aspectj
     * .lang. ProceedingJoinPoint, edu.utah.further.core.api.chain.ChainRequest,
     * edu.utah.further.core.api.chain.RequestProcessor)
     */
    @Override
    public Object doAround(final ProceedingJoinPoint pjp, final ChainRequest request,
            final RequestProcessor requestProcessor) {
        printPreProcessorReport(request, requestProcessor);
        Object retVal = null;
        final long startTime = System.nanoTime();
        try {
            // Execute and time the processor
            retVal = pjp.proceed();
            final long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);

            // Inject the proper status processor based the POST-request-processor query
            // state
            final QueryContext postQueryContext = request.getAttribute(QUERY_CONTEXT);
            final StatusType statusType = StatusType.valueOf(postQueryContext.isFailed());
            notifyStatus(request, requestProcessor, statusType, durationMillis);
            if (statusType.isFailed()) {
                // Change return value of the processor's method to terminate chain
                retVal = Boolean.TRUE;
            }
        } catch (final Throwable throwable) {
            final long durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
            // Take control of the request and execute a terminating processor; pass
            // the exception on to the status reporter
            request.setException(throwable);
            final QueryContext queryContext = request.getAttribute(QUERY_CONTEXT);
            final ResultContext resultContext = queryContext.getResultContext();
            if (throwable.getClass().isAssignableFrom(AccessDeniedException.class)) {
                resultContext.setNumRecords(ResultContext.ACCESS_DENIED);
            } else {
                // Override all errors to be -1 results so that partial results are not
                // displayed or zero is not displayed.
                resultContext.setNumRecords(-1);
            }

            final String exceptionString = getExceptionAsString(throwable);
            log.error("QpMonitorAdvice caught the following exception in processor " + requestProcessor.getName()
                    + ": ", exceptionString);

            notifyStatus(request, requestProcessor, StatusType.FAIL, durationMillis);
            // Change return value of the processor's method to terminate chain
            retVal = Boolean.TRUE;
        }
        return retVal;
    }

    // ========================= GETTERS & SETTERS =========================

    // ========================= PRIVATE METHODS ===========================

    /**
     * @param request
     * @param requestProcessor
     * @param statusType
     * @param durationMillis
     *            duration of the process described by this status, in milliseconds
     */
    private final void notifyStatus(final ChainRequest request, final RequestProcessor requestProcessor,
            final StatusType statusType, final long durationMillis) {
        // Decide whether to fail query based on the current status
        final QueryContext queryContext = request.getAttribute(QUERY_CONTEXT);
        final DsMetaData dsMetaData = request.getAttribute(META_DATA);

        boolean queryFailed = false;

        try {
            if (log.isDebugEnabled()) {
                log.debug(dsMetaData.getName() + ": notifying current status " + queryContext + " "
                        + requestProcessor.getName() + " " + statusType);
            }
            queryFailed = queryContext.isFailed();
            if (statusType.isFailed() && !queryFailed) {
                if (log.isDebugEnabled()) {
                    log.debug("Failing query");
                }
                queryContext.fail();
            }

            // Notify other components of the new status
            statusReporter.notify(queryContext);

            if (log.isInfoEnabled()) {
                log.info(String.format("Time [%-30s] %.3g sec, %s", StringUtil.getNameNullSafe(requestProcessor),
                        new Double((1.0d * durationMillis) / ONE_SECOND), statusType));
            }
        } catch (final Exception e) {
            log.error("An exception occurred while updating status", e);
            queryContext.fail();
        } finally {
            // In the event that anything above fails, this ensures that
            // Update query context status
            logException(request);
            setCurrentStatus(request, requestProcessor, statusType, durationMillis);
            logQueryStatus(queryContext, dsMetaData, queryFailed);
        }

    }

    /**
     * @param request
     */
    private void logException(final ChainRequest request) {
        final Throwable exception = request.getException();
        if (exception != null) {
            log.error("An exception occurred during query processing", exception);
        }
    }

    /**
     * @param queryContext
     * @param queryFailed
     */
    private void logQueryStatus(final QueryContext queryContext, final DsMetaData dsMetaData,
            final boolean queryFailed) {
        if (log.isDebugEnabled()) {
            final String mark = queryFailed ? "%" : "*";
            log.debug(StringUtils.repeat(mark, 40));
            final StatusMetaData status = queryContext.getCurrentStatus();
            log.debug(status != null ? (dsMetaData.getName() + ": " + status.getStatus()) : Strings.NULL_TO_STRING);
            log.debug(StringUtils.repeat(mark, 40));
        }
    }

    /**
     * @param throwable
     * @return
     */
    private static String getExceptionAsString(final Throwable throwable) {
        String exceptionString = throwable.toString();
        if (ReflectionUtil.instanceOf(throwable, ApplicationException.class)) {
            final ApplicationException error = (ApplicationException) throwable;
            if (error.getCode() != null) {
                if (error.getCode().isRecoverable()) {
                    exceptionString = throwable.getClass() + ": " + throwable.getMessage();
                }
            }

        }
        return exceptionString;
    }
}