de.metas.ui.web.config.WebuiExceptionHandler.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.config.WebuiExceptionHandler.java

Source

package de.metas.ui.web.config;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import de.metas.logging.LogManager;
import de.metas.ui.web.login.exceptions.NotLoggedInException;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2017 metas 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, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

/**
 * Handles all REST API exceptions
 *
 * @author metas-dev <dev@metasfresh.com>
 * @author based on {@link org.springframework.boot.autoconfigure.web.DefaultErrorAttributes}
 */
@Component
// Order: IMPORTANT: because we want to call this handler before any other. Else, if it's the last one added, it might be that it will be never called
@Order(Ordered.HIGHEST_PRECEDENCE)
public class WebuiExceptionHandler implements ErrorAttributes, HandlerExceptionResolver {

    private static final transient Logger logger = LogManager.getLogger(WebuiExceptionHandler.class);

    private static final String REQUEST_ATTR_EXCEPTION = WebuiExceptionHandler.class.getName() + ".ERROR";

    private static final String ATTR_Timestamp = "timestamp";
    private static final String ATTR_Status = "status";
    private static final String ATTR_Error = "error";
    private static final String ATTR_Exception = "exception";
    private static final String ATTR_Message = "message";
    private static final String ATTR_Stacktrace = "trace";
    private static final String ATTR_Path = "path";

    @Value("${de.metas.ui.web.config.WebuiExceptionHandler.logExceptions:true}")
    private boolean logExceptions;

    private final Set<Class<?>> EXCEPTIONS_ExcludeFromLogging = ImmutableSet.of(NotLoggedInException.class);

    private final Map<Class<?>, HttpStatus> EXCEPTION_HTTPSTATUS = ImmutableMap.<Class<?>, HttpStatus>builder()
            .put(org.elasticsearch.client.transport.NoNodeAvailableException.class, HttpStatus.SERVICE_UNAVAILABLE)
            .build();

    @Override
    public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        logExceptionIfNeeded(ex, handler);
        request.setAttribute(REQUEST_ATTR_EXCEPTION, ex);
        return null;
    }

    private void logExceptionIfNeeded(final Exception ex, final Object handler) {
        if (!logExceptions) {
            return;
        }

        if (isExcludeFromLogging(ex)) {
            logger.debug("Got REST (excluded from logging) exception from handler={}", handler, ex);
        } else {
            logger.warn("Got REST exception from handler={}", handler, ex);
        }
    }

    private final boolean isExcludeFromLogging(final Throwable ex) {
        for (final Class<?> exceptionClass : EXCEPTIONS_ExcludeFromLogging) {
            if (exceptionClass.isAssignableFrom(ex.getClass())) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Map<String, Object> getErrorAttributes(final RequestAttributes requestAttributes,
            final boolean includeStackTrace) {
        final Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put(ATTR_Timestamp, new Date());
        addStatus(errorAttributes, requestAttributes);
        addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
        addPath(errorAttributes, requestAttributes);
        return errorAttributes;
    }

    private void addStatus(final Map<String, Object> errorAttributes, final RequestAttributes requestAttributes) {
        Integer status = null;

        //
        // Extract HTTP status from EXCEPTION_HTTPSTATUS map
        final Throwable error = getError(requestAttributes);
        if (error != null) {
            final Class<? extends Throwable> errorClass = error.getClass();
            status = EXCEPTION_HTTPSTATUS.entrySet().stream().filter(e -> isErrorMatching(e.getKey(), errorClass))
                    .map(e -> e.getValue().value()).findFirst().orElse(null);
        }

        //
        // Extract HTTP status from attributes
        if (status == null) {
            status = getAttribute(requestAttributes, RequestDispatcher.ERROR_STATUS_CODE);
        }

        if (status == null) {
            errorAttributes.put(ATTR_Status, 999);
            errorAttributes.put(ATTR_Error, "None");
            return;
        }
        errorAttributes.put(ATTR_Status, status);
        try {
            errorAttributes.put(ATTR_Error, HttpStatus.valueOf(status).getReasonPhrase());
        } catch (final Exception ex) {
            // Unable to obtain a reason
            errorAttributes.put(ATTR_Error, "Http Status " + status);
        }
    }

    private final boolean isErrorMatching(final Class<?> baseClass, final Class<?> clazz) {
        return baseClass.isAssignableFrom(clazz);
    }

    private void addErrorDetails(final Map<String, Object> errorAttributes,
            final RequestAttributes requestAttributes, final boolean includeStackTrace) {
        Throwable error = getError(requestAttributes);
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = ((ServletException) error).getCause();
            }
            errorAttributes.put(ATTR_Exception, error.getClass().getName());
            addErrorMessage(errorAttributes, error);
            if (includeStackTrace && !isExcludeFromLogging(error)) {
                addStackTrace(errorAttributes, error);
            }
        }

        final Object message = getAttribute(requestAttributes, RequestDispatcher.ERROR_MESSAGE);
        if ((!StringUtils.isEmpty(message) || errorAttributes.get(ATTR_Message) == null)
                && !(error instanceof BindingResult)) {
            errorAttributes.put(ATTR_Message, StringUtils.isEmpty(message) ? "No message available" : message);
        }
    }

    private void addErrorMessage(final Map<String, Object> errorAttributes, final Throwable error) {
        errorAttributes.put(ATTR_Message, error.getLocalizedMessage());
    }

    private void addStackTrace(final Map<String, Object> errorAttributes, final Throwable error) {
        final StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put(ATTR_Stacktrace, stackTrace.toString());
    }

    private void addPath(final Map<String, Object> errorAttributes, final RequestAttributes requestAttributes) {
        final String path = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);
        if (path != null) {
            errorAttributes.put(ATTR_Path, path);
        }
    }

    @Override
    public Throwable getError(final RequestAttributes requestAttributes) {
        Throwable exception = getAttribute(requestAttributes, REQUEST_ATTR_EXCEPTION);
        if (exception == null) {
            exception = getAttribute(requestAttributes, RequestDispatcher.ERROR_EXCEPTION);
        }
        return exception;
    }

    @SuppressWarnings("unchecked")
    private static <T> T getAttribute(final RequestAttributes requestAttributes, final String name) {
        return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }

}