/*
Copyright 2011-2014 Red Hat, Inc
This file is part of PressGang CCMS.
PressGang CCMS 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 3 of the License, or
(at your option) any later version.
PressGang CCMS 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 PressGang CCMS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jboss.pressgang.ccms.server.rest.v1.utils;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.sql.BatchUpdateException;
import java.sql.SQLException;
import org.jboss.pressgang.ccms.model.base.PressGangEntity;
import org.jboss.pressgang.ccms.model.exceptions.CustomConstraintViolationException;
import org.jboss.pressgang.ccms.provider.exception.ProviderException;
import org.jboss.pressgang.ccms.rest.v1.entities.base.RESTBaseEntityV1;
import org.jboss.pressgang.ccms.server.rest.v1.EntityCache;
import org.jboss.pressgang.ccms.utils.common.ExceptionUtilities;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.InternalServerErrorException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RESTv1Utilities {
private static final Logger LOG = LoggerFactory.getLogger(RESTv1Utilities.class);
public static <U extends PressGangEntity, T extends RESTBaseEntityV1<T>> U findEntity(final EntityManager entityManager,
EntityCache entityCache, final T restEntity, final Class<U> databaseClass) {
if (restEntity.getId() == null) {
// If the id is null than there is no possible way a matching entity could be found, so return null
return null;
} else if (restEntity.getId() < 0 && entityCache.containsRESTEntity(restEntity)) {
// If the id is less than zero than it is a new entity, so get the matching entity from the cache
return (U) entityCache.get(restEntity);
} else {
// At this stage the id will be positive, so get the entity from the Persistence Context
return entityManager.find(databaseClass, restEntity.getId());
}
}
/**
* Process an Error/Exception and generate a RESTEasy Exception based on the error/exception produced.
*
* @param ex The Error/Exception to be processed.
* @return A RESTEasy Exception containing the details of the Error.
*/
public static Failure processError(final Throwable ex) {
return processError(null, ex);
}
/**
* Process an Error/Exception and generate a RESTEasy Exception based on the error/exception produced.
*
* @param transaction The transaction to handle rolling back changes.
* @param ex The Error/Exception to be processed.
* @return A RESTEasy Exception containing the details of the Error.
*/
public static Failure processError(final UserTransaction transaction, final Throwable ex) {
LOG.error("Failed to process REST request", ex);
// Rollback if a transaction is active
try {
if (transaction != null) {
/*
Rolling back only active transactions leads to "Error checking for a transaction" and
"Transaction is not active" errors.
From http://techblogs.agiledigital.com.au/2013/01/03/jboss-as-7-1-transaction-reaping/
Transaction information is stored in the thread context. Each connector can potentially share the
same transaction – even across requests. The transaction is removed from the thread context when it
is committed or rolled back.
When the transaction is reaped, it is marked as rollback only. This changes the status of the
transaction to STATUS_ROLLING_BACK – and then shortly thereafter STATUS_ROLLED_BACK. However, the
transaction is not actually rolled back or removed from the context of the thread. It will not be
removed until utx.commit() or utx.rollback() is called.
The base servlet would never attempt to commit or rollback the transaction because:
(utx.getStatus() == Status.STATUS_ACTIVE)
will always be false after the transaction reaper has caused the status to change to ROLLED_BACK.
Consequently, the transaction was never removed from the thread context and an attempt would be made
by TxConnectionManagerImpl (seen in the stack trace above) to re-use it. Since it had been marked
ROLLED_BACK it could not be re-used and an exception was thrown.
*/
final int status = transaction.getStatus();
if (status != Status.STATUS_NO_TRANSACTION) {
transaction.rollback();
}
}
} catch (Throwable e) {
return new InternalServerErrorException(e);
}
// We need to do some unwrapping of exception first
Throwable cause = ex;
while (cause != null) {
if (cause == cause.getCause()) {
// sometimes this can be an circular reference
break;
} else if (cause instanceof Failure) {
return (Failure) cause;
} else if (cause instanceof EntityNotFoundException) {
return new NotFoundException(cause);
} else if (cause instanceof ValidationException || cause instanceof CustomConstraintViolationException || cause instanceof
org.hibernate.exception.ConstraintViolationException || cause instanceof RollbackException) {
break;
} else if (cause instanceof PersistenceException) {
if (cause.getCause() != null && cause.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
cause = cause.getCause();
} else {
break;
}
} else if (cause instanceof ProviderException) {
if (cause != null && (cause instanceof ValidationException || cause instanceof PersistenceException || cause instanceof
CustomConstraintViolationException)) {
cause = cause.getCause();
} else {
break;
}
} else if (cause instanceof BatchUpdateException) {
cause = ((SQLException) cause).getNextException();
} else {
cause = cause.getCause();
}
}
// This is a Persistence exception with information
if (cause instanceof ConstraintViolationException) {
final ConstraintViolationException e = (ConstraintViolationException) cause;
final StringBuilder stringBuilder = new StringBuilder();
// Construct a "readable" message outlining the validation errors
for (ConstraintViolation invalidValue : e.getConstraintViolations())
stringBuilder.append(invalidValue.getMessage()).append("\n");
return new BadRequestException(stringBuilder.toString(), cause);
} else if (cause instanceof EntityNotFoundException) {
return new NotFoundException(cause);
} else if (cause instanceof org.hibernate.exception.ConstraintViolationException) {
return new BadRequestException(ExceptionUtilities.getRootCause(cause).getMessage());
} else if (cause instanceof ValidationException) {
return new BadRequestException(cause);
} else if (cause instanceof CustomConstraintViolationException) {
return new BadRequestException(cause.getMessage());
} else if (cause instanceof RollbackException) {
return new BadRequestException(
"This is most likely caused by the fact that two users are trying to save the same entity at the same time.\n" + "You" +
" can try saving again, or reload the entity to see if there were any changes made in the background.", cause);
} else if (cause instanceof ProviderException) {
if (cause instanceof org.jboss.pressgang.ccms.provider.exception.NotFoundException) {
throw new NotFoundException(cause);
} else if (cause instanceof org.jboss.pressgang.ccms.provider.exception.InternalServerErrorException) {
throw new InternalServerErrorException(cause);
} else if (cause instanceof org.jboss.pressgang.ccms.provider.exception.BadRequestException) {
throw new BadRequestException(cause);
} else if (cause instanceof org.jboss.pressgang.ccms.provider.exception.UnauthorisedException) {
throw new UnauthorizedException(cause);
}
}
// If it's not some validation error then it must be an internal error.
return new InternalServerErrorException(ex);
}
}