blob: 6d87c909b93c5810c0b86d131cd61566eec2bba6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence;
import java.lang.reflect.InvocationTargetException;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.MixedLockLevels;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.LockException;
import org.apache.openjpa.util.NoTransactionException;
import org.apache.openjpa.util.ObjectExistsException;
import org.apache.openjpa.util.ObjectNotFoundException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.QueryException;
import org.apache.openjpa.util.RuntimeExceptionTranslator;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UserException;
/**
* Converts from OpenJPA to persistence exception types.
*
* @author Abe White
* @author Marc Prud'hommeaux
*/
public class PersistenceExceptions
extends Exceptions {
public static final RuntimeExceptionTranslator TRANSLATOR =
new RuntimeExceptionTranslator() {
public RuntimeException translate(RuntimeException re) {
return PersistenceExceptions.toPersistenceException(re);
}
};
/**
* Returns a {@link RuntimeExceptionTranslator} that will perform
* the correct exception translation as well as roll back the
* current transaction when for all but {@link NoResultException}
* and {@link NonUniqueResultException} in accordance with
* section 3.7 of the EJB 3.0 specification.
*/
public static RuntimeExceptionTranslator getRollbackTranslator(
final OpenJPAEntityManager em) {
return new RuntimeExceptionTranslator() {
private boolean throwing = false;
public RuntimeException translate(RuntimeException re) {
RuntimeException ex = toPersistenceException(re);
if (!(ex instanceof NonUniqueResultException)
&& !(ex instanceof NoResultException)
&& !(ex instanceof LockTimeoutException)
&& !(ex instanceof QueryTimeoutException)
&& !throwing) {
try {
throwing = true;
if (em.isOpen() && ((EntityManagerImpl) em).isActive())
((EntityManagerImpl) em).setRollbackOnly(ex);
} finally {
// handle re-entrancy
throwing = false;
}
}
return ex;
}
};
}
/**
* Convert the given throwable to the proper persistence exception.
*/
public static RuntimeException toPersistenceException(Throwable t) {
return (RuntimeException) translateException(t, true);
}
/**
* Translate the given exception.
*
* @param checked whether to translate checked exceptions
*/
private static Throwable translateException(Throwable t, boolean checked) {
if (isPersistenceException(t))
return t;
// immediately throw errors
if (t instanceof Error)
throw (Error) t;
OpenJPAException ke;
if (!(t instanceof OpenJPAException)) {
if (!checked || t instanceof RuntimeException)
return t;
ke = new org.apache.openjpa.util.GeneralException(t.getMessage());
ke.setStackTrace(t.getStackTrace());
return ke;
}
// if only nested exception is a persistence one, return it directly
ke = (OpenJPAException) t;
if (ke.getNestedThrowables().length == 1
&& isPersistenceException(ke.getCause()))
return ke.getCause();
// RuntimeExceptions thrown from callbacks should be thrown directly
if (ke.getType() == OpenJPAException.USER
&& ke.getSubtype() == UserException.CALLBACK
&& ke.getNestedThrowables().length == 1) {
Throwable e = ke.getCause();
if (e instanceof InvocationTargetException)
e = e.getCause();
if (e instanceof RuntimeException)
return e;
}
// perform intelligent translation of openjpa exceptions
switch (ke.getType()) {
case OpenJPAException.STORE:
return translateStoreException(ke);
case OpenJPAException.USER:
return translateUserException(ke);
case OpenJPAException.WRAPPED:
return translateWrappedException(ke);
default:
return translateGeneralException(ke);
}
}
/**
* Translate the given store exception.
*/
private static Throwable translateStoreException(OpenJPAException ke) {
Exception e;
int subtype = ke.getSubtype();
String msg = ke.getMessage();
Throwable[] nested = getNestedThrowables(ke);
Object failed = getFailedObject(ke);
boolean fatal = ke.isFatal();
Throwable cause = (ke.getNestedThrowables() != null
&& ke.getNestedThrowables().length == 1)
? ke.getNestedThrowables()[0] : null;
if (subtype == StoreException.OBJECT_NOT_FOUND || cause instanceof ObjectNotFoundException) {
e = new org.apache.openjpa.persistence.EntityNotFoundException(msg, nested, failed, fatal);
} else if (subtype == StoreException.OPTIMISTIC || cause instanceof OptimisticException) {
e = new org.apache.openjpa.persistence.OptimisticLockException(msg, nested, failed, fatal);
} else if (subtype == StoreException.LOCK || cause instanceof LockException) {
LockException lockEx = (LockException) (ke instanceof LockException ? ke : cause);
if (lockEx != null && lockEx.getLockLevel() >= MixedLockLevels.LOCK_PESSIMISTIC_READ) {
if (!lockEx.isFatal()) {
e = new org.apache.openjpa.persistence.LockTimeoutException(msg, nested, failed);
} else {
e = new org.apache.openjpa.persistence.PessimisticLockException(msg, nested, failed);
}
} else {
e = new org.apache.openjpa.persistence.OptimisticLockException(msg, nested, failed, fatal);
}
} else if (subtype == StoreException.OBJECT_EXISTS || cause instanceof ObjectExistsException) {
e = new org.apache.openjpa.persistence.EntityExistsException(msg, nested, failed, fatal);
} else if (subtype == StoreException.QUERY || cause instanceof QueryException) {
QueryException queryEx = (QueryException) (ke instanceof QueryException ? ke : cause);
if (!queryEx.isFatal()) {
e = new org.apache.openjpa.persistence.QueryTimeoutException(msg, nested, failed, false);
} else {
e = new org.apache.openjpa.persistence.PersistenceException(msg, nested, failed, true);
}
} else {
e = new org.apache.openjpa.persistence.PersistenceException(msg, nested, failed, fatal);
}
e.setStackTrace(ke.getStackTrace());
return e;
}
/**
* Translate the given user exception.
* If a {link {@link OpenJPAException#getSubtype() sub type} is set on the
* given exception then a corresponding facade-level exception i.e. the
* exceptions that inherit JPA-defined exceptions is generated.
* If given exception is not further classified to a sub type, then
* an [@link {@link #translateInternalException(OpenJPAException)} attempt}
* is made to translate the given OpenJPAException by its internal cause.
*/
private static Exception translateUserException(OpenJPAException ke) {
Exception e;
switch (ke.getSubtype()) {
case UserException.NO_TRANSACTION:
e = new
org.apache.openjpa.persistence.TransactionRequiredException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
case UserException.NO_RESULT:
e = new org.apache.openjpa.persistence.NoResultException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
case UserException.NON_UNIQUE_RESULT:
e = new org.apache.openjpa.persistence.NonUniqueResultException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
case UserException.INVALID_STATE:
e = new org.apache.openjpa.persistence.InvalidStateException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
default:
e = translateCause(ke);
}
e.setStackTrace(ke.getStackTrace());
return e;
}
/*
* Translate the given wrapped exception. If contains an Exception, return
* the exception. If contains a Throwable, wrap the throwable and
* return it.
*/
private static Exception translateWrappedException(OpenJPAException ke) {
Throwable t = ke.getCause();
if (t instanceof Exception)
return (Exception)t;
return translateCause(ke);
}
/**
* Translate to a facade-level exception if the given exception
* a) has a cause i.e. one and only nested Throwable
* and b) that cause is one of the known internal exception which has a
* direct facade-level counterpart
* (for example, ObjectNotFoundException can be translated to
* EntityNotFoundException).
* If the above conditions are not met then return generic
* ArgumentException.
*
* In either case, preserve all the details.
*/
private static Exception translateCause(OpenJPAException ke) {
Throwable cause = ke.getCause();
if (cause instanceof ObjectNotFoundException) {
return new EntityNotFoundException(
ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
} else if (cause instanceof ObjectExistsException) {
return new EntityExistsException(
ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
} else if (cause instanceof NoTransactionException) {
return new TransactionRequiredException(
ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
} else if (cause instanceof OptimisticException) {
return new OptimisticLockException(
ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
} else {
return new org.apache.openjpa.persistence.ArgumentException(
ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
}
}
/**
* Translate the given general exception.
*/
private static Throwable translateGeneralException(OpenJPAException ke) {
Exception e = new org.apache.openjpa.persistence.PersistenceException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
e.setStackTrace(ke.getStackTrace());
return e;
}
/**
* Return true if the given exception is a persistence exception.
*/
private static boolean isPersistenceException(Throwable t) {
return t.getClass().getName()
.startsWith("org.apache.openjpa.persistence.");
}
/**
* Translate the nested throwables of the given openjpa exception into
* nested throwables for a persistence exception.
*/
private static Throwable[] getNestedThrowables(OpenJPAException ke) {
Throwable[] nested = ke.getNestedThrowables();
if (nested.length == 0)
return nested;
Throwable[] trans = new Throwable[nested.length];
for (int i = 0; i < nested.length; i++)
trans[i] = translateException(nested[i], false);
return trans;
}
/**
* Return the failed object for the given exception, performing any
* necessary conversions.
*/
private static Object getFailedObject(OpenJPAException ke) {
Object o = ke.getFailedObject();
if (o == null)
return null;
if (o instanceof Broker)
return JPAFacadeHelper.toEntityManager((Broker) o);
return JPAFacadeHelper.fromOpenJPAObjectId(o);
}
/**
* Helper method to extract a nested exception from an internal nested
* array in a safe way.
*/
static Throwable getCause(Throwable[] nested) {
if (nested == null || nested.length == 0)
return null;
return nested[0];
}
}