/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.api.common.jaxb; import java.util.Map; import javax.annotation.Resource; import javax.persistence.NoResultException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.apache.commons.lang.StringUtils; import org.orcid.core.api.OrcidApiConstants; import org.orcid.core.exception.ExceedMaxNumberOfElementsException; import org.orcid.core.exception.OrcidApiException; import org.orcid.core.exception.OrcidCoreExceptionMapper; import org.orcid.core.exception.OrcidDeprecatedException; import org.orcid.core.exception.OrcidInvalidScopeException; import org.orcid.core.exception.OrcidValidationException; import org.orcid.core.locale.LocaleManager; import org.orcid.core.manager.OrcidSecurityManager; import org.orcid.core.manager.impl.OrcidUrlManager; import org.orcid.core.oauth.OAuthError; import org.orcid.core.oauth.OAuthErrorUtils; import org.orcid.core.security.aop.LockedException; import org.orcid.core.version.ApiSection; import org.orcid.core.web.filters.ApiVersionFilter; import org.orcid.jaxb.model.message.DeprecatedDate; import org.orcid.jaxb.model.message.ErrorDesc; import org.orcid.jaxb.model.message.Orcid; import org.orcid.jaxb.model.message.OrcidDeprecated; import org.orcid.jaxb.model.message.OrcidMessage; import org.orcid.jaxb.model.message.PrimaryRecord; import org.orcid.pojo.ajaxForm.PojoUtil; import org.orcid.utils.DateUtils; import org.orcid.utils.OrcidStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.sun.jersey.api.NotFoundException; /** * orcid-api - Nov 8, 2011 - OrcidExceptionMapper * * @author Declan Newman (declan) */ @Provider @Consumes(value = { OrcidApiConstants.VND_ORCID_JSON, OrcidApiConstants.VND_ORCID_XML, OrcidApiConstants.ORCID_JSON, OrcidApiConstants.ORCID_XML, MediaType.APPLICATION_XML, MediaType.WILDCARD, MediaType.APPLICATION_JSON }) @Produces(value = { OrcidApiConstants.VND_ORCID_JSON, OrcidApiConstants.VND_ORCID_XML, OrcidApiConstants.ORCID_JSON, OrcidApiConstants.ORCID_XML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Component public class OrcidExceptionMapper implements ExceptionMapper<Throwable> { private static final Logger LOGGER = LoggerFactory.getLogger(OrcidExceptionMapper.class); private static final String LOCATION_HEADER = "location"; private static final String OAUTH_TOKEN_REQUEST = "/oauth/token"; @Context private HttpServletRequest httpRequest; @Resource private MessageSource messageSource; @Resource private LocaleManager localeManager; @Resource private OrcidCoreExceptionMapper orcidCoreExceptionMapper; @Resource private OrcidSecurityManager securityManager; @Override public Response toResponse(Throwable t) { // Whatever exception has been caught, make sure we log it. String clientId = securityManager.getClientIdFromAPIRequest(); if (PojoUtil.isEmpty(clientId)) { LOGGER.error("An exception has occured, no client id info provided", t); } else { if (t instanceof NotFoundException) { StringBuffer temp = new StringBuffer("An exception has occured processing request from client ").append(clientId).append(". ").append(t.getMessage()); LOGGER.error(temp.toString()); } else { LOGGER.error("An exception has occured processing request from client " + clientId, t); } } if (isOAuthTokenRequest()) { return oAuthErrorResponse(t); } String apiVersion = getApiVersion(); if (!PojoUtil.isEmpty(apiVersion)) { switch (apiVersion) { case OrcidCoreExceptionMapper.V2: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2); case OrcidCoreExceptionMapper.V2_RC1: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2_RC1); case OrcidCoreExceptionMapper.V2_RC2: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2_RC2); case OrcidCoreExceptionMapper.V2_RC3: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2_RC3); case OrcidCoreExceptionMapper.V2_RC4: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2_RC4); } } // If there was no api version, check if it is notifications or a 1.2 // error type switch (getApiSection()) { case NOTIFICATIONS: return newStyleErrorResponse(t, OrcidCoreExceptionMapper.V2); default: return legacyErrorResponse(t); } } private Response oAuthErrorResponse(Throwable t) { OAuthError error = OAuthErrorUtils.getOAuthError(t); return Response.status(error.getResponseStatus()).entity(error).build(); } private Response legacyErrorResponse(Throwable t) { if (OrcidApiException.class.isAssignableFrom(t.getClass())) { return ((OrcidApiException) t).getResponse(); } else if (OrcidValidationException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Bad Request: ", t); return Response.status(Response.Status.BAD_REQUEST).entity(entity).build(); } else if (NotFoundException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Please specify a version number (1.2 or higher) : ", t); return Response.status(OrcidCoreExceptionMapper.getHttpStatusAndErrorCode(t).getKey()).entity(entity).build(); } else if (WebApplicationException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacy500OrcidEntity(t); WebApplicationException webException = (WebApplicationException) t; return Response.status(webException.getResponse().getStatus()).entity(entity).build(); } else if (AuthenticationException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Authentication problem : ", t); return Response.status(Response.Status.UNAUTHORIZED).entity(entity).build(); } else if (OAuth2Exception.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("OAuth2 problem : ", t); return Response.status(Response.Status.UNAUTHORIZED).entity(entity).build(); } else if (OrcidInvalidScopeException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("OAuth2 problem : ", t); return Response.status(Response.Status.UNAUTHORIZED).entity(entity).build(); } else if (SecurityException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Security problem : ", t); return Response.status(Response.Status.FORBIDDEN).entity(entity).build(); } else if (IllegalStateException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Illegal state : ", t); return Response.status(Response.Status.FORBIDDEN).entity(entity).build(); } else if (IllegalArgumentException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Bad Request : ", t); return Response.status(Response.Status.BAD_REQUEST).entity(entity).build(); } else if (OrcidDeprecatedException.class.isAssignableFrom(t.getClass())) { OrcidDeprecatedException exception = (OrcidDeprecatedException) t; OrcidDeprecated depreciatedError = new OrcidDeprecated(); Map<String, String> params = exception.getParams(); String location = null; if (params != null) { if (params.containsKey(OrcidDeprecatedException.ORCID)) { PrimaryRecord pr = new PrimaryRecord(); pr.setOrcid(new Orcid(params.get(OrcidDeprecatedException.ORCID))); depreciatedError.setPrimaryRecord(pr); location = getPrimaryRecordLocation(params); } if (params.containsKey(OrcidDeprecatedException.DEPRECATED_DATE)) { DeprecatedDate dd = new DeprecatedDate(); String dateString = params.get(OrcidDeprecatedException.DEPRECATED_DATE); dd.setValue(DateUtils.convertToXMLGregorianCalendar(dateString, false)); depreciatedError.setDate(dd); } } Response response = null; if (location != null) { response = Response.status(Response.Status.MOVED_PERMANENTLY).header(LOCATION_HEADER, location).entity(depreciatedError).build(); } else { response = Response.status(Response.Status.MOVED_PERMANENTLY).entity(depreciatedError).build(); } return response; } else if (LockedException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Account locked : ", t); return Response.status(Response.Status.CONFLICT).entity(entity).build(); } else if (NoResultException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity("Not found : ", t); return Response.status(Response.Status.NOT_FOUND).entity(entity).build(); } else if (ExceedMaxNumberOfElementsException.class.isAssignableFrom(t.getClass())) { OrcidMessage entity = getLegacyOrcidEntity( "This version of the API does not support adding more than 10,000 works to a record. Please consider using the 2.0 API.", null); return Response.status(Response.Status.CONFLICT).entity(entity).build(); } else { OrcidMessage entity = getLegacy500OrcidEntity(t); return Response.status(OrcidCoreExceptionMapper.getHttpStatusAndErrorCode(t).getKey()).entity(entity).build(); } } private OrcidMessage getLegacy500OrcidEntity(Throwable e) { OrcidMessage entity = new OrcidMessage(); entity.setMessageVersion(OrcidMessage.DEFAULT_VERSION); entity.setErrorDesc(new ErrorDesc( StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : messageSource.getMessage("apiError.unknown.exception", null, localeManager.getLocale()))); return entity; } private OrcidMessage getLegacyOrcidEntity(String prefix, Throwable e) { OrcidMessage entity = new OrcidMessage(); entity.setMessageVersion(OrcidMessage.DEFAULT_VERSION); if (e != null && !PojoUtil.isEmpty(e.getMessage())) entity.setErrorDesc(new ErrorDesc(prefix + e.getMessage())); else entity.setErrorDesc(new ErrorDesc(prefix)); return entity; } private Response newStyleErrorResponse(Throwable t, String version) { if (WebApplicationException.class.isAssignableFrom(t.getClass())) { return getOrcidErrorResponse((WebApplicationException) t, version); } else { return getOrcidErrorResponse(t, version); } } private Response getOrcidErrorResponse(WebApplicationException e, String version) { int status = e.getResponse().getStatus(); return getOrcidErrorResponse(9001, status, e, version); } private Response getOrcidErrorResponse(Integer errorCode, Integer status, Throwable t, String version) { Object orcidError = orcidCoreExceptionMapper.getOrcidError(errorCode, status, t, version); return getOrcidErrorResponse(orcidError, t); } private Response getOrcidErrorResponse(Throwable t, String version) { Object orcidError = orcidCoreExceptionMapper.getOrcidError(t, version); return getOrcidErrorResponse(orcidError, t); } private Response getOrcidErrorResponse(Object orcidError, Throwable t) { int statusCode = 0; if (org.orcid.jaxb.model.error_rc1.OrcidError.class.isAssignableFrom(orcidError.getClass())) { statusCode = ((org.orcid.jaxb.model.error_rc1.OrcidError) orcidError).getResponseCode(); } else if (org.orcid.jaxb.model.error_rc2.OrcidError.class.isAssignableFrom(orcidError.getClass())) { statusCode = ((org.orcid.jaxb.model.error_rc2.OrcidError) orcidError).getResponseCode(); } else if (org.orcid.jaxb.model.error_rc3.OrcidError.class.isAssignableFrom(orcidError.getClass())) { statusCode = ((org.orcid.jaxb.model.error_rc3.OrcidError) orcidError).getResponseCode(); } else if (org.orcid.jaxb.model.error_rc4.OrcidError.class.isAssignableFrom(orcidError.getClass())) { statusCode = ((org.orcid.jaxb.model.error_rc4.OrcidError) orcidError).getResponseCode(); } else if (org.orcid.jaxb.model.error_v2.OrcidError.class.isAssignableFrom(orcidError.getClass())) { statusCode = ((org.orcid.jaxb.model.error_v2.OrcidError) orcidError).getResponseCode(); } if (OrcidDeprecatedException.class.isAssignableFrom(t.getClass())) { OrcidDeprecatedException exception = (OrcidDeprecatedException) t; Map<String, String> params = exception.getParams(); String location = null; if (params != null) { if (params.containsKey(OrcidDeprecatedException.ORCID)) { location = getPrimaryRecordLocation(params); } } Response response = null; if (location != null) { response = Response.status(statusCode).header(LOCATION_HEADER, location).entity(orcidError).build(); } else { response = Response.status(statusCode).entity(orcidError).build(); } return response; } return Response.status(statusCode).entity(orcidError).build(); } private ApiSection getApiSection() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ApiSection apiSection = (ApiSection) requestAttributes.getAttribute(ApiVersionFilter.API_SECTION_REQUEST_ATTRIBUTE_NAME, RequestAttributes.SCOPE_REQUEST); return apiSection != null ? apiSection : ApiSection.V1; } private String getApiVersion() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); String apiVersion = (String) requestAttributes.getAttribute(ApiVersionFilter.API_VERSION_REQUEST_ATTRIBUTE_NAME, RequestAttributes.SCOPE_REQUEST); return apiVersion; } private boolean isOAuthTokenRequest() { String url = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getRequestURL().toString(); return url.endsWith(OAUTH_TOKEN_REQUEST); } /** * Returns the location of the primary record for a deprecated record */ private String getPrimaryRecordLocation(Map<String, String> params) { String deprecatedOrcid = OrcidStringUtils.getOrcidNumber(httpRequest.getRequestURI()); String primaryOrcid = OrcidStringUtils.getOrcidNumber(params.get(OrcidDeprecatedException.ORCID)); String originalRequest = httpRequest.getRequestURL().toString(); if (OrcidUrlManager.isSecure(httpRequest)) { if (originalRequest.startsWith("http:")) { originalRequest = originalRequest.replaceFirst("http:", "https:"); } } return originalRequest.replace(deprecatedOrcid, primaryOrcid); } }