/* * 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]; } }