CommonResponseEntityExceptionHandler.java

/*
 * This file is part of Openrouteservice.
 *
 * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this library;
 * if not, see <https://www.gnu.org/licenses/>.
 */

package org.heigit.ors.api.errors;

import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
import org.apache.log4j.Logger;
import org.heigit.ors.api.util.AppInfo;
import org.heigit.ors.exceptions.ParameterValueException;
import org.heigit.ors.exceptions.StatusCodeException;
import org.heigit.ors.exceptions.UnknownParameterException;
import org.heigit.ors.isochrones.IsochronesErrorCodes;
import org.heigit.ors.util.DebugUtility;
import org.json.simple.JSONObject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
@RestController
public class CommonResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    private static final String EXCEPTION_MESSAGE = "Exception";
    private static final Logger LOCAL_LOGGER = Logger.getLogger(CommonResponseEntityExceptionHandler.class.getName());

    final int errorCodeBase;

    public CommonResponseEntityExceptionHandler() { // required for Tomcat
        errorCodeBase = 0;
    }

    public CommonResponseEntityExceptionHandler(int errorCodeBase) {
        this.errorCodeBase = errorCodeBase;
    }

    public ResponseEntity handleStatusCodeException(StatusCodeException exception) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        logException(exception);
        return new ResponseEntity(constructErrorBody(exception), headers, convertOrsToSpringHttpCode(exception.getStatusCode()));
    }

    public ResponseEntity handleUnknownParameterException(UnknownParameterException exception) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        logException(exception);
        return new ResponseEntity(constructErrorBody(exception), headers, convertOrsToSpringHttpCode(exception.getStatusCode()));
    }

    public ResponseEntity handleGenericException(Exception exception) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        logException(exception);
        Throwable cause = exception.getCause();
        if (cause instanceof ValueInstantiationException e) {
            if (e.getCause() instanceof StatusCodeException origExc) {
                return new ResponseEntity(constructErrorBody(origExc), headers, HttpStatus.BAD_REQUEST);
            }
            return new ResponseEntity(constructErrorBody(new ParameterValueException(IsochronesErrorCodes.INVALID_PARAMETER_VALUE, "")), headers, HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity(constructGenericErrorBody(exception), headers, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private void logException(Exception exception) {
        if (DebugUtility.isDebug()) {
            if (LOCAL_LOGGER.isDebugEnabled()) {
                // Log also the stack trace
                LOCAL_LOGGER.error(EXCEPTION_MESSAGE, exception);
            } else {
                // Log only the error message
                LOCAL_LOGGER.error(exception);
            }
        }
    }

    private HttpStatus convertOrsToSpringHttpCode(int statusCode) {
        return HttpStatus.valueOf(statusCode);
    }

    private String constructErrorBody(StatusCodeException exception) {
        int errorCode = exception.getInternalCode();
        if (errorCode < 100) {
            errorCode = errorCodeBase + errorCode;
        }

        JSONObject json = constructJsonBody(exception, errorCode);

        return json.toString();
    }

    private JSONObject constructJsonBody(Exception exception, int internalErrorCode) {
        JSONObject json = new JSONObject();

        JSONObject jError = new JSONObject();
        jError.put("message", exception.getMessage());

        if (internalErrorCode > 0) {
            jError.put("code", internalErrorCode);
        }

        json.put("error", jError);

        JSONObject jInfo = new JSONObject();
        jInfo.put("engine", AppInfo.getEngineInfo());
        jInfo.put("timestamp", System.currentTimeMillis());
        json.put("info", jInfo);

        return json;
    }

    private String constructGenericErrorBody(Exception exception) {
        return constructJsonBody(exception, -1).toString();
    }
}