blob: b1e87177d82e0b0aad6bfc7d3ce81c0b7af0eb74 [file] [log] [blame]
/*
* Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// gonural - initial implementation
package org.eclipse.persistence.jpa.rs.exceptions;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jpa.rs.DataStorage;
import org.eclipse.persistence.jpa.rs.features.ServiceVersion;
import org.eclipse.persistence.jpa.rs.util.JPARSLogger;
import org.eclipse.persistence.jpa.rs.util.StreamingOutputMarshaller;
import javax.naming.NamingException;
import jakarta.persistence.EntityExistsException;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.NoResultException;
import jakarta.persistence.NonUniqueResultException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.PessimisticLockException;
import jakarta.persistence.QueryTimeoutException;
import jakarta.persistence.RollbackException;
import jakarta.persistence.TransactionRequiredException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriInfo;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractExceptionMapper {
@Context
private HttpHeaders headers;
@Context
UriInfo uriInfo;
private static final Map<String, Status> HTTP_STATUS_CODE_MAPPING =
Collections.unmodifiableMap(new HashMap<>() {
{
put(ClassNotFoundException.class.getName(), Status.BAD_REQUEST);
put(ConversionException.class.getName(), Status.BAD_REQUEST);
put(DatabaseException.class.getName(), Status.INTERNAL_SERVER_ERROR);
put(EntityExistsException.class.getName(), Status.CONFLICT);
put(EntityNotFoundException.class.getName(), Status.NOT_FOUND);
put(IOException.class.getName(), Status.BAD_REQUEST);
put(IllegalAccessException.class.getName(), Status.BAD_REQUEST);
put(IllegalArgumentException.class.getName(), Status.BAD_REQUEST);
put(IllegalStateException.class.getName(), Status.BAD_REQUEST);
put(InvocationTargetException.class.getName(), Status.INTERNAL_SERVER_ERROR);
put(JAXBException.class.getName(), Status.NOT_FOUND); // TODO: we might want to change this http status in v2.0
put(MalformedURLException.class.getName(), Status.BAD_REQUEST);
put(NamingException.class.getName(), Status.BAD_REQUEST);
put(NoResultException.class.getName(), Status.NOT_FOUND);
put(NoSuchMethodException.class.getName(), Status.BAD_REQUEST);
put(NonUniqueResultException.class.getName(), Status.NOT_FOUND);
put(NumberFormatException.class.getName(), Status.BAD_REQUEST);
put(OptimisticLockException.class.getName(), Status.INTERNAL_SERVER_ERROR);
put(PersistenceException.class.getName(), Status.INTERNAL_SERVER_ERROR);
put(PessimisticLockException.class.getName(), Status.INTERNAL_SERVER_ERROR);
put(QueryTimeoutException.class.getName(), Status.BAD_REQUEST);// TODO: we might want to change this http status in v2.0
put(RollbackException.class.getName(), Status.BAD_REQUEST);
put(TransactionRequiredException.class.getName(), Status.INTERNAL_SERVER_ERROR);
}
});
// An absolute URI that identifies the problem type. When dereferenced, it SHOULD provide human-readable documentation for the problem type (e.g., using HTML)."
private static final String PROBLEM_TYPE = "http://www.eclipse.org/eclipselink/documentation/";
protected Response buildResponse(JPARSException exception) {
String path = null;
exception.setHttpStatusCode(getHttpStatusCode(exception.getCause()));
if (uriInfo != null) {
URI requestURI = uriInfo.getRequestUri();
if (requestURI != null) {
path = requestURI.getPath();
}
}
if ((path != null) && (path.contains(ServiceVersion.VERSION_2_0.getCode()) || path.contains(ServiceVersion.LATEST.getCode()))) {
ErrorResponse errorResponse = new ErrorResponse(PROBLEM_TYPE, exception.getMessage(), String.valueOf(exception.getErrorCode()));
errorResponse.setRequestId((String) DataStorage.get(DataStorage.REQUEST_ID));
errorResponse.setHttpStatus(exception.getHttpStatusCode().getStatusCode());
String error = marshallErrorResponse(errorResponse, StreamingOutputMarshaller.getResponseMediaType(headers).toString());
if (error != null) {
return Response.status(exception.getHttpStatusCode()).entity(error).type(StreamingOutputMarshaller.getResponseMediaType(headers)).build();
}
}
return Response.status(exception.getHttpStatusCode()).type(StreamingOutputMarshaller.getResponseMediaType(headers)).build();
}
private static Status getHttpStatusCode(Throwable throwable) {
if (throwable != null) {
Status httpStatusCode = HTTP_STATUS_CODE_MAPPING.get(throwable.getClass().getName());
if (throwable instanceof RollbackException) {
Throwable cause = throwable.getCause();
if (cause != null) {
if (cause instanceof DatabaseException) {
// 409 Conflict ("The request could not be completed due to a conflict with the current state of the resource.")
httpStatusCode = Status.CONFLICT;
}
}
}
if (httpStatusCode != null) {
return httpStatusCode;
}
}
return Status.BAD_REQUEST;
}
private String marshallErrorResponse(ErrorResponse errorResponse, String mediaType) {
try {
JAXBContext context = (JAXBContext) JAXBContextFactory.createContext(new Class[] { ErrorResponse.class }, null);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, mediaType);
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, Boolean.FALSE);
marshaller.setProperty(MarshallerProperties.JSON_REDUCE_ANY_ARRAYS, true);
StringWriter writer = new StringWriter();
marshaller.marshal(errorResponse, writer);
return writer.toString();
} catch (Exception ex) {
JPARSLogger.exception(ex.getMessage(), null, ex);
}
return null;
}
}