OPENJPA-458: Amplified SQL Exception processing. Introduced a new sql-error-state-codes.xml to specify database specific error code for different types of Store exceptions. This helps to narrow SQL exception to a specific errors which bubble up to user application.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@668814 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2008-06-17 20:07:12 +00:00
parent e00c6e8f80
commit a8e0dc180e
10 changed files with 533 additions and 117 deletions

View File

@ -53,6 +53,7 @@ import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
@ -101,7 +102,11 @@ import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.LockException;
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.ReferentialIntegrityException;
import org.apache.openjpa.util.Serialization;
import org.apache.openjpa.util.StoreException;
@ -158,16 +163,6 @@ public class DBDictionary
private static final String ZERO_TIMESTAMP_STR =
"'" + new Timestamp(0) + "'";
public static final List EMPTY_STRING_LIST = Arrays.asList(new String[]{});
public static final List[] SQL_STATE_CODES =
{EMPTY_STRING_LIST, // 0: Default
Arrays.asList(new String[]{"41000"}), // 1: LOCK
EMPTY_STRING_LIST, // 2: OBJECT_NOT_FOUND
EMPTY_STRING_LIST, // 3: OPTIMISTIC
Arrays.asList(new String[]{"23000"}), // 4: REFERENTIAL_INTEGRITY
EMPTY_STRING_LIST // 5: OBJECT_EXISTS
};
private static final Localizer _loc = Localizer.forPackage
(DBDictionary.class);
@ -364,6 +359,9 @@ public class DBDictionary
// any positive number = batch limit
public int batchLimit = NO_BATCH;
public final Map<Integer,Set<String>> sqlStateCodes =
new HashMap<Integer, Set<String>>();
public DBDictionary() {
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT",
@ -4109,8 +4107,32 @@ public class DBDictionary
if (selectWords != null)
selectWordSet.addAll(Arrays.asList(Strings.split(selectWords
.toUpperCase(), ",", 0)));
// initialize the error codes
SQLErrorCodeReader codeReader = new SQLErrorCodeReader();
String rsrc = "sql-error-state-codes.xml";
InputStream stream = getClass().getResourceAsStream(rsrc);
String dictionaryClassName = getClass().getName();
if (stream == null) { // User supplied dictionary but no error codes xml
stream = DBDictionary.class.getResourceAsStream(rsrc); // use default
dictionaryClassName = getClass().getSuperclass().getName();
}
codeReader.parse(stream, dictionaryClassName, this);
}
public void addErrorCode(int errorType, String errorCode) {
if (errorCode == null || errorCode.trim().length() == 0)
return;
Set<String> codes = sqlStateCodes.get(errorType);
if (codes == null) {
codes = new HashSet<String>();
codes.add(errorCode.trim());
sqlStateCodes.put(errorType, codes);
} else {
codes.add(errorCode.trim());
}
}
//////////////////////////////////////
// ConnectionDecorator implementation
//////////////////////////////////////
@ -4119,7 +4141,7 @@ public class DBDictionary
* Decorate the given connection if needed. Some databases require special
* handling for JDBC bugs. This implementation issues any
* {@link #initializationSQL} that has been set for the dictionary but
* does not decoreate the connection.
* does not decorate the connection.
*/
public Connection decorate(Connection conn)
throws SQLException {
@ -4170,7 +4192,7 @@ public class DBDictionary
public OpenJPAException newStoreException(String msg, SQLException[] causes,
Object failed) {
if (causes != null && causes.length > 0) {
OpenJPAException ret = SQLExceptions.narrow(msg, causes[0], this);
OpenJPAException ret = narrow(msg, causes[0]);
ret.setFailedObject(failed).setNestedThrowables(causes);
return ret;
}
@ -4179,26 +4201,36 @@ public class DBDictionary
}
/**
* Gets the list of String, each represents an error that can help
* to narrow down a SQL exception to specific type of StoreException.<br>
* For example, error code <code>"23000"</code> represents referential
* integrity violation and hence can be narrowed down to
* {@link ReferentialIntegrityException} rather than more general
* {@link StoreException}.<br>
* JDBC Drivers are not uniform in return values of SQLState for the same
* error and hence each database specific Dictionary can specialize.<br>
*
*
* @return an <em>unmodifiable</em> list of Strings representing supposedly
* uniform SQL States for a given type of StoreException.
* Default behavior is to return an empty list.
* Gets the subtype of StoreException by matching the given SQLException's
* error state code to the list of error codes supplied by the dictionary.
* Returns -1 if no matching code can be found.
*/
public List/*<String>*/ getSQLStates(int exceptionType) {
if (exceptionType>=0 && exceptionType<SQL_STATE_CODES.length)
return SQL_STATE_CODES[exceptionType];
return EMPTY_STRING_LIST;
OpenJPAException narrow(String msg, SQLException ex) {
String errorState = ex.getSQLState();
int errorType = StoreException.GENERAL;
for (Integer type : sqlStateCodes.keySet()) {
Set<String> erroStates = sqlStateCodes.get(type);
if (erroStates != null && erroStates.contains(errorState)) {
errorType = type;
break;
}
}
switch (errorType) {
case StoreException.LOCK:
return new LockException(msg);
case StoreException.OBJECT_EXISTS:
return new ObjectExistsException(msg);
case StoreException.OBJECT_NOT_FOUND:
return new ObjectNotFoundException(msg);
case StoreException.OPTIMISTIC:
return new OptimisticException(msg);
case StoreException.REFERENTIAL_INTEGRITY:
return new ReferentialIntegrityException(msg);
default:
return new StoreException(msg);
}
}
/**
* Closes the specified {@link DataSource} and releases any
* resources associated with it.

View File

@ -100,26 +100,4 @@ public class DerbyDictionary
}
}
}
/**
* Adds extra SQLState code that Derby JDBC Driver uses. In JDBC 4.0,
* SQLState will follow either XOPEN or SQL 2003 convention. A compliant
* driver can be queries via DatabaseMetaData.getSQLStateType() to detect
* the convention type.<br>
* This method is overwritten to highlight that a) the SQL State is ideally
* uniform across JDBC Drivers but not practically and b) the overwritten
* method must crate a new list to return as the super classes list is
* unmodifable.
*/
public List getSQLStates(int exceptionType) {
List original = super.getSQLStates(exceptionType);
if (exceptionType == StoreException.LOCK) {
// Can not add new codes to unmodifable list of the super class
List newStates = new ArrayList(original);
newStates.add("40XL1");
return newStates;
}
return original;
}
}

View File

@ -0,0 +1,136 @@
package org.apache.openjpa.jdbc.sql;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.xml.XMLFactory;
import org.apache.openjpa.util.StoreException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Parses XML content of SQL Error State codes to populate errro codes for
* a given Database Dictionary.
*
* @author Pinaki Poddar
*
*/
public class SQLErrorCodeReader {
private Log log = null;
public static final String ERROR_CODE_DELIMITER = ",";
public static final Map<String, Integer> storeErrorTypes =
new HashMap<String, Integer>();
static {
storeErrorTypes.put("lock", StoreException.LOCK);
storeErrorTypes.put("object-exists", StoreException.OBJECT_EXISTS);
storeErrorTypes
.put("object-not-found", StoreException.OBJECT_NOT_FOUND);
storeErrorTypes.put("optimistic", StoreException.OPTIMISTIC);
storeErrorTypes.put("referential-integrity",
StoreException.REFERENTIAL_INTEGRITY);
}
private static final Localizer _loc =
Localizer.forPackage(SQLErrorCodeReader.class);
public List<String> getDictionaries(InputStream in) {
List<String> result = new ArrayList<String>();
DocumentBuilder builder = XMLFactory.getDOMParser(false, false);
try {
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
NodeList nodes = root.getElementsByTagName("dictionary");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node dictionary = attrs.getNamedItem("class");
if (dictionary != null) {
result.add(dictionary.getNodeValue());
}
}
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.error(_loc.get("error-code-parse-error"));
}
} finally {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
return result;
}
/**
* Parses given stream of XML content for error codes of the given database
* dictionary name. Populates the given dictionary with the error codes.
*
*/
public void parse(InputStream in, String dictName, DBDictionary dict) {
if (in == null || dict == null)
return;
log = dict.conf.getLog(JDBCConfiguration.LOG_JDBC);
DocumentBuilder builder = XMLFactory.getDOMParser(false, false);
try {
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
NodeList nodes = root.getElementsByTagName("dictionary");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node dictionary = attrs.getNamedItem("class");
if (dictionary != null
&& dictionary.getNodeValue().equals(dictName)) {
readErrorCodes(node, dict);
}
}
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.error(_loc.get("error-code-parse-error"));
}
} finally {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
static void readErrorCodes(Node node, DBDictionary dict) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
short nodeType = child.getNodeType();
if (nodeType == Node.ELEMENT_NODE) {
String errorType = child.getNodeName();
if (storeErrorTypes.containsKey(errorType)) {
String errorCodes = child.getTextContent();
if (!StringUtils.isEmpty(errorCodes)) {
String[] codes = errorCodes.split(ERROR_CODE_DELIMITER);
for (String code : codes) {
dict.addErrorCode(storeErrorTypes.get(errorType),
code.trim());
}
}
}
}
}
}
}

View File

@ -117,32 +117,4 @@ public class SQLExceptions {
}
return (SQLException[]) errs.toArray(new SQLException[errs.size()]);
}
/**
* Narrows the given SQLException to a specific type of
* {@link StoreException#getSubtype() StoreException} by analyzing the
* SQLState code supplied by SQLException. Each database-specific
* {@link DBDictionary dictionary} can supply a set of error codes that will
* map to a specific specific type of StoreException via
* {@link DBDictionary#getSQLStates(int) getSQLStates()} method.
* The default behavior is to return generic {@link StoreException
* StoreException}.
*/
public static OpenJPAException narrow(String msg, SQLException se,
DBDictionary dict) {
String e = se.getSQLState();
if (dict.getSQLStates(StoreException.LOCK).contains(e))
return new LockException(msg);
else if (dict.getSQLStates(StoreException.OBJECT_EXISTS).contains(e))
return new ObjectExistsException(msg);
else if (dict.getSQLStates(StoreException.OBJECT_NOT_FOUND).contains(e))
return new ObjectNotFoundException(msg);
else if (dict.getSQLStates(StoreException.OPTIMISTIC).contains(e))
return new OptimisticException(msg);
else if (dict.getSQLStates(StoreException.REFERENTIAL_INTEGRITY)
.contains(e))
return new ReferentialIntegrityException(msg);
else
return new StoreException(msg);
}
}

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<!-- ======================================================================= -->
<!-- Lists SQL Error State codes for specific type of faults per database -->
<!-- dictionary. -->
<!-- SQL Exceptions raised by JDBC should carry standard error state code -->
<!-- but they hardly do. Hence, the error state codes for specific type of -->
<!-- database error can be listed here. These codes help to narrow down the -->
<!-- cause of failure at JDBC layer and bubbles up as more intelligible -->
<!-- exception to the application. -->
<!-- ======================================================================= -->
<sql-state-codes>
<dictionary class="org.apache.openjpa.jdbc.sql.DB2Dictionary">
<lock>-911,-913</lock>
<referential-integrity>-407,-530,-531,-532,-543,-544,-545,-603,-667,-803</referential-integrity>
<object-exists></object-exists>
<object-not-found></object-not-found>
<optimistic></optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.DerbyDictionary">
<lock>40001</lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists>23505</object-exists>
<object-not-found></object-not-found>
<optimistic>40XL1,40001</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.SQLServerDictionary">
<lock>1205</lock>
<referential-integrity>544,2601,2627,8114,8115</referential-integrity>
<optimistic>1205</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.SybaseDictionary">
<lock>1205</lock>
<referential-integrity>423,511,515,530,547,2601,2615,2714</referential-integrity>
<object-exists></object-exists>
<object-not-found></object-not-found>
<optimistic>1205</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.AccessDictionary">
<lock>40001</lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists>23505,456c</object-exists>
<object-not-found></object-not-found>
<optimistic>40XL1,40001</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.CacheDictionary">
<lock>40001</lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists>23505,456c</object-exists>
<object-not-found></object-not-found>
<optimistic>40XL1,40001</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.EmpressDictionary">
<lock>40001</lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists>23505,456c</object-exists>
<object-not-found></object-not-found>
<optimistic>40XL1,40001</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.FoxProDictionary">
<lock>40001</lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists>23505,456c</object-exists>
<object-not-found></object-not-found>
<optimistic>40XL1,40001</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.H2Dictionary">
<referential-integrity>22003,22012,22025,23000,23001</referential-integrity>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.HSQLDictionary">
<referential-integrity>-9</referential-integrity>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.InformixDictionary">
<referential-integrity>-239,-268,-692,-11030</referential-integrity>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.InterbaseDictionary">
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.FirebirdDictionary">
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.JDataStoreDictionary">
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.MySQLDictionary">
<lock>1213</lock>
<referential-integrity>630,839,840,893,1062,1169,1215,1216,1217,1451,1452,1557</referential-integrity>
<object-exists>23000</object-exists>
<object-not-found></object-not-found>
<optimistic>41000,1205,1213</optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.OracleDictionary">
<lock></lock>
<referential-integrity>1,1400,1722,2291,2292</referential-integrity>
<object-exists></object-exists>
<object-not-found></object-not-found>
<optimistic></optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.PointbaseDictionary">
<lock></lock>
<referential-integrity>22001,22005,23502,23503,23513,X0Y32</referential-integrity>
<object-exists></object-exists>
<object-not-found></object-not-found>
<optimistic></optimistic>
</dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.PostgresDictionary">
<lock>55P03,40P01</lock>
<referential-integrity>23000,23502,23503,23505,23514</referential-integrity>
<object-exists></object-exists>
<object-not-found></object-not-found>
<optimistic>55P03</optimistic>
</dictionary>
</sql-state-codes>

View File

@ -30,7 +30,7 @@ import org.apache.openjpa.persistence.StoreCacheImpl;
import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwned;
import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwner;
import org.apache.openjpa.persistence.common.utils.AbstractTestCase;
import org.apache.openjpa.persistence.exception.PObject;
import org.apache.openjpa.persistence.datacache.common.apps.PObject;
/**
* Tests various application behavior with or without DataCache.

View File

@ -0,0 +1,57 @@
/*
* 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.datacache.common.apps;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
/**
* A Simple entity for testing. Has a version field for testing optimistic
* concurrent usage.
*
* @author Pinaki Poddar
*
*/
@Entity
public class PObject {
@Id
@GeneratedValue
private long id;
private String name;
@Version
private int version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getId() {
return id;
}
public int getVersion() {
return version;
}
}

View File

@ -33,7 +33,6 @@ import javax.persistence.Version;
@Entity
public class PObject {
@Id
@GeneratedValue
private long id;
private String name;
@Version
@ -47,6 +46,10 @@ public class PObject {
this.name = name;
}
public void setId(long id) {
this.id = id;
}
public long getId() {
return id;
}

View File

@ -18,17 +18,32 @@
*/
package org.apache.openjpa.persistence.exception;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException;
import javax.persistence.TransactionRequiredException;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.SQLErrorCodeReader;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* Tests proper JPA exceptions are raised by the implementation.
* Actual runtime type of the raised exception is a subclass of JPA-defined
* exception.
* The raised exception may nest the expected exception.
*
* @author Pinaki Poddar
*/
public class TestException extends SingleEMFTestCase {
public void setUp() {
private static long ID_COUNTER = System.currentTimeMillis();
public void setUp() {
super.setUp(PObject.class, CLEAR_TABLES);
}
@ -36,16 +51,17 @@ public class TestException extends SingleEMFTestCase {
* Tests that when Optimistic transaction consistency is violated, the
* exception thrown is an instance of javax.persistence.OptimisticException.
*/
public void testThrowsJPADefinedOptimisticException() {
public void testThrowsOptimisticException() {
EntityManager em1 = emf.createEntityManager();
EntityManager em2 = emf.createEntityManager();
assertNotEquals(em1, em2);
em1.getTransaction().begin();
PObject pc = new PObject();
long id = ++ID_COUNTER;
pc.setId(id);
em1.persist(pc);
em1.getTransaction().commit();
Object id = pc.getId();
em1.clear();
em1.getTransaction().begin();
@ -61,47 +77,121 @@ public class TestException extends SingleEMFTestCase {
try {
pc2.setName("Modified in TXN2");
em2.flush();
fail("Expected optimistic exception on flush");
fail("Expected " + OptimisticLockException.class);
} catch (Throwable t) {
if (!isExpectedException(t, OptimisticLockException.class)) {
print(t);
fail(t.getCause().getClass() + " is not " +
OptimisticLockException.class);
}
assertException(t, OptimisticLockException.class);
}
em1.getTransaction().commit();
try {
em2.getTransaction().commit();
fail("Expected optimistic exception on commit");
fail("Expected " + OptimisticLockException.class);
} catch (Throwable t) {
if (!isExpectedException(t, OptimisticLockException.class)) {
print(t);
fail(t.getCause().getClass() + " is not " +
OptimisticLockException.class);
assertException(t, OptimisticLockException.class);
}
}
public void testThrowsEntityExistsException() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
PObject pc = new PObject();
long id = ++ID_COUNTER;
pc.setId(id);
em.persist(pc);
em.getTransaction().commit();
em.clear();
em.getTransaction().begin();
PObject pc2 = new PObject();
pc2.setId(id);
em.persist(pc2);
try {
em.getTransaction().commit();
fail("Expected " + EntityExistsException.class);
} catch (Throwable t) {
assertException(t, EntityExistsException.class);
}
}
public void testThrowsEntityNotFoundException() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
PObject pc = new PObject();
long id = ++ID_COUNTER;
pc.setId(id);
em.persist(pc);
em.getTransaction().commit();
EntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
PObject pc2 = em2.find(PObject.class, id);
assertNotNull(pc2);
em2.remove(pc2);
em2.getTransaction().commit();
try {
em.refresh(pc);
fail("Expected " + EntityNotFoundException.class);
} catch (Throwable t) {
assertException(t, EntityNotFoundException.class);
}
}
public void testErrorCodeConfigurationHasAllKnownDictionaries() {
SQLErrorCodeReader reader = new SQLErrorCodeReader();
InputStream in = DBDictionary.class.getResourceAsStream
("sql-error-state-codes.xml");
assertNotNull(in);
List<String> names = reader.getDictionaries(in);
assertTrue(names.size()>=18);
for (String name:names) {
try {
Class.forName(name, false, Thread.currentThread()
.getContextClassLoader());
} catch (Throwable t) {
fail("DB dictionary " + name + " can not be loaded");
t.printStackTrace();
}
}
}
boolean isExpectedException(Throwable t, Class expectedType) {
if (t == null) return false;
if (expectedType.isAssignableFrom(t.getClass()))
return true;
if (t.getCause()==t) return false;
return isExpectedException(t.getCause(), expectedType);
/**
* Asserts that the given expected type of the exception is equal to or a
* subclass of the given throwable or any of its nested exception.
* Otherwise fails assertion and prints the given throwable and its nested
* exception on the console.
*/
void assertException(Throwable t, Class expectedType) {
if (!isExpectedException(t, expectedType)) {
t.printStackTrace();
print(t, 0);
fail(t + " or its cause is not instanceof " + expectedType);
}
}
void print(Throwable t) {
print(t, 0);
/**
* Affirms if the given expected type of the exception is equal to or a
* subclass of the given throwable or any of its nested exception.
*/
boolean isExpectedException(Throwable t, Class expectedType) {
if (t == null)
return false;
if (expectedType.isAssignableFrom(t.getClass()))
return true;
return isExpectedException(t.getCause(), expectedType);
}
void print(Throwable t, int tab) {
if (t == null) return;
for (int i=0; i<tab*4;i++) System.out.print(" ");
String sqlState = (t instanceof SQLException) ?
"(SQLState=" + ((SQLException)t).getSQLState() + ":" + t.getMessage() + ")":"";
"(SQLState=" + ((SQLException)t).getSQLState() + ":"
+ t.getMessage() + ")" : "";
System.out.println(t.getClass().getName() + sqlState);
if (t.getCause()==t) return;
if (t.getCause() == t)
return;
print(t.getCause(), tab+1);
}
}

View File

@ -143,24 +143,26 @@ public class PersistenceExceptions
*/
private static Throwable translateStoreException(OpenJPAException ke) {
Exception e;
switch (ke.getSubtype()) {
case StoreException.OBJECT_NOT_FOUND:
Throwable cause = (ke.getNestedThrowables() != null
&& ke.getNestedThrowables().length == 1)
? ke.getNestedThrowables()[0] : null;
if (ke.getSubtype() == StoreException.OBJECT_NOT_FOUND
|| cause instanceof ObjectNotFoundException) {
e = new org.apache.openjpa.persistence.EntityNotFoundException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
case StoreException.OPTIMISTIC:
case StoreException.LOCK:
e = new org.apache.openjpa.persistence.OptimisticLockException
} else if (ke.getSubtype() == StoreException.OPTIMISTIC
|| ke.getSubtype() == StoreException.LOCK
|| cause instanceof OptimisticException) {
e = new org.apache.openjpa.persistence.OptimisticLockException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
case StoreException.OBJECT_EXISTS:
} else if (ke.getSubtype() == StoreException.OBJECT_EXISTS
|| cause instanceof ObjectExistsException) {
e = new org.apache.openjpa.persistence.EntityExistsException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());
break;
default:
} else {
e = new org.apache.openjpa.persistence.PersistenceException
(ke.getMessage(), getNestedThrowables(ke),
getFailedObject(ke), ke.isFatal());