/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.module.webservices.rest.web.v1_0.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.openmrs.api.APIAuthenticationException; import org.openmrs.api.context.Context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.module.webservices.rest.SimpleObject; import org.openmrs.module.webservices.rest.web.RestConstants; import org.openmrs.module.webservices.rest.web.RestUtil; import org.openmrs.module.webservices.validation.ValidationException; import org.springframework.stereotype.Controller; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.openmrs.module.webservices.rest.web.response.ConversionException; /** * Resource controllers should extend this base class to have standard exception handling done * automatically. (This is necessary to send error messages as HTTP statuses rather than just as * html content, as the core web application does.) */ @Controller @RequestMapping(value = "/rest/**") public class BaseRestController { private final int DEFAULT_ERROR_CODE = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; private static final String DISABLE_WWW_AUTH_HEADER_NAME = "Disable-WWW-Authenticate"; private final String DEFAULT_ERROR_DETAIL = ""; private final Log log = LogFactory.getLog(getClass()); /** * @should return unauthorized if not logged in * @should return forbidden if logged in */ @ExceptionHandler(APIAuthenticationException.class) @ResponseBody public SimpleObject apiAuthenticationExceptionHandler(Exception ex, HttpServletRequest request, HttpServletResponse response) throws Exception { int errorCode; String errorDetail; if (Context.isAuthenticated()) { // user is logged in but doesn't have the relevant privilege -> 403 FORBIDDEN errorCode = HttpServletResponse.SC_FORBIDDEN; errorDetail = "User is logged in but doesn't have the relevant privilege"; } else { // user is not logged in -> 401 UNAUTHORIZED errorCode = HttpServletResponse.SC_UNAUTHORIZED; errorDetail = "User is not logged in"; if (shouldAddWWWAuthHeader(request)) { response.addHeader("WWW-Authenticate", "Basic realm=\"OpenMRS at " + RestConstants.URI_PREFIX + "\""); } } response.setStatus(errorCode); return RestUtil.wrapErrorResponse(ex, errorDetail); } @ExceptionHandler(ValidationException.class) @ResponseBody public SimpleObject validationExceptionHandler(ValidationException validationException, HttpServletRequest request, HttpServletResponse response) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return RestUtil.wrapValidationErrorResponse(validationException); } /** * Handle ConvertionException - return response with exception message - ConversionUtil throws * ConversionException */ @ExceptionHandler(ConversionException.class) @ResponseBody public SimpleObject conversionExceptionHandler(ConversionException conversionException, HttpServletRequest request, HttpServletResponse response) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return RestUtil.wrapErrorResponse(conversionException, ""); } @ExceptionHandler(Exception.class) @ResponseBody public SimpleObject handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) throws Exception { int errorCode = DEFAULT_ERROR_CODE; String errorDetail = DEFAULT_ERROR_DETAIL; ResponseStatus ann = ex.getClass().getAnnotation(ResponseStatus.class); if (ann != null) { errorCode = ann.value().value(); if (StringUtils.isNotEmpty(ann.reason())) { errorDetail = ann.reason(); } } else if (RestUtil.hasCause(ex, APIAuthenticationException.class)) { return apiAuthenticationExceptionHandler(ex, request, response); } else if (ex.getClass() == HttpRequestMethodNotSupportedException.class) { errorCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED; } if (errorCode >= 500) { // if it's a server error, we log it at a high level of importance log.error(ex.getMessage(), ex); } else { // 4xx client errors are logged at a lower level of importance log.info(ex.getMessage(), ex); } response.setStatus(errorCode); return RestUtil.wrapErrorResponse(ex, errorDetail); } private boolean shouldAddWWWAuthHeader(HttpServletRequest request) { return request.getHeader(DISABLE_WWW_AUTH_HEADER_NAME) == null || !request.getHeader(DISABLE_WWW_AUTH_HEADER_NAME).equals("true"); } /** * It should be overridden if you want to expose resources under a different URL than /rest/v1. * * @return the namespace */ public String getNamespace() { return RestConstants.VERSION_1; } public String buildResourceName(String resource) { String namespace = getNamespace(); if (StringUtils.isBlank(namespace)) { return resource; } else { if (namespace.startsWith("/")) { namespace = namespace.substring(1); } if (!namespace.endsWith("/")) { namespace += "/"; } return namespace + resource; } } }