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.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; 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.GeneralException;
import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.InvalidStateException; 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.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.ReferentialIntegrityException; import org.apache.openjpa.util.ReferentialIntegrityException;
import org.apache.openjpa.util.Serialization; import org.apache.openjpa.util.Serialization;
import org.apache.openjpa.util.StoreException; import org.apache.openjpa.util.StoreException;
@ -158,16 +163,6 @@ public class DBDictionary
private static final String ZERO_TIMESTAMP_STR = private static final String ZERO_TIMESTAMP_STR =
"'" + new Timestamp(0) + "'"; "'" + 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 private static final Localizer _loc = Localizer.forPackage
(DBDictionary.class); (DBDictionary.class);
@ -364,6 +359,9 @@ public class DBDictionary
// any positive number = batch limit // any positive number = batch limit
public int batchLimit = NO_BATCH; public int batchLimit = NO_BATCH;
public final Map<Integer,Set<String>> sqlStateCodes =
new HashMap<Integer, Set<String>>();
public DBDictionary() { public DBDictionary() {
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{ fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT", "BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT",
@ -4109,8 +4107,32 @@ public class DBDictionary
if (selectWords != null) if (selectWords != null)
selectWordSet.addAll(Arrays.asList(Strings.split(selectWords selectWordSet.addAll(Arrays.asList(Strings.split(selectWords
.toUpperCase(), ",", 0))); .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 // ConnectionDecorator implementation
////////////////////////////////////// //////////////////////////////////////
@ -4119,7 +4141,7 @@ public class DBDictionary
* Decorate the given connection if needed. Some databases require special * Decorate the given connection if needed. Some databases require special
* handling for JDBC bugs. This implementation issues any * handling for JDBC bugs. This implementation issues any
* {@link #initializationSQL} that has been set for the dictionary but * {@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) public Connection decorate(Connection conn)
throws SQLException { throws SQLException {
@ -4170,7 +4192,7 @@ public class DBDictionary
public OpenJPAException newStoreException(String msg, SQLException[] causes, public OpenJPAException newStoreException(String msg, SQLException[] causes,
Object failed) { Object failed) {
if (causes != null && causes.length > 0) { 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); ret.setFailedObject(failed).setNestedThrowables(causes);
return ret; return ret;
} }
@ -4179,26 +4201,36 @@ public class DBDictionary
} }
/** /**
* Gets the list of String, each represents an error that can help * Gets the subtype of StoreException by matching the given SQLException's
* to narrow down a SQL exception to specific type of StoreException.<br> * error state code to the list of error codes supplied by the dictionary.
* For example, error code <code>"23000"</code> represents referential * Returns -1 if no matching code can be found.
* 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.
*/ */
public List/*<String>*/ getSQLStates(int exceptionType) { OpenJPAException narrow(String msg, SQLException ex) {
if (exceptionType>=0 && exceptionType<SQL_STATE_CODES.length) String errorState = ex.getSQLState();
return SQL_STATE_CODES[exceptionType]; int errorType = StoreException.GENERAL;
return EMPTY_STRING_LIST; 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 * Closes the specified {@link DataSource} and releases any
* resources associated with it. * 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()]); 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.BidirectionalOne2OneOwned;
import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwner; import org.apache.openjpa.persistence.cache.common.apps.BidirectionalOne2OneOwner;
import org.apache.openjpa.persistence.common.utils.AbstractTestCase; 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. * 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 @Entity
public class PObject { public class PObject {
@Id @Id
@GeneratedValue
private long id; private long id;
private String name; private String name;
@Version @Version
@ -47,6 +46,10 @@ public class PObject {
this.name = name; this.name = name;
} }
public void setId(long id) {
this.id = id;
}
public long getId() { public long getId() {
return id; return id;
} }

View File

@ -18,17 +18,32 @@
*/ */
package org.apache.openjpa.persistence.exception; package org.apache.openjpa.persistence.exception;
import java.io.InputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException; 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; import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/** /**
* Tests proper JPA exceptions are raised by the implementation. * 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 class TestException extends SingleEMFTestCase {
public void setUp() { private static long ID_COUNTER = System.currentTimeMillis();
public void setUp() {
super.setUp(PObject.class, CLEAR_TABLES); super.setUp(PObject.class, CLEAR_TABLES);
} }
@ -36,16 +51,17 @@ public class TestException extends SingleEMFTestCase {
* Tests that when Optimistic transaction consistency is violated, the * Tests that when Optimistic transaction consistency is violated, the
* exception thrown is an instance of javax.persistence.OptimisticException. * exception thrown is an instance of javax.persistence.OptimisticException.
*/ */
public void testThrowsJPADefinedOptimisticException() { public void testThrowsOptimisticException() {
EntityManager em1 = emf.createEntityManager(); EntityManager em1 = emf.createEntityManager();
EntityManager em2 = emf.createEntityManager(); EntityManager em2 = emf.createEntityManager();
assertNotEquals(em1, em2); assertNotEquals(em1, em2);
em1.getTransaction().begin(); em1.getTransaction().begin();
PObject pc = new PObject(); PObject pc = new PObject();
long id = ++ID_COUNTER;
pc.setId(id);
em1.persist(pc); em1.persist(pc);
em1.getTransaction().commit(); em1.getTransaction().commit();
Object id = pc.getId();
em1.clear(); em1.clear();
em1.getTransaction().begin(); em1.getTransaction().begin();
@ -61,47 +77,121 @@ public class TestException extends SingleEMFTestCase {
try { try {
pc2.setName("Modified in TXN2"); pc2.setName("Modified in TXN2");
em2.flush(); em2.flush();
fail("Expected optimistic exception on flush"); fail("Expected " + OptimisticLockException.class);
} catch (Throwable t) { } catch (Throwable t) {
if (!isExpectedException(t, OptimisticLockException.class)) { assertException(t, OptimisticLockException.class);
print(t);
fail(t.getCause().getClass() + " is not " +
OptimisticLockException.class);
}
} }
em1.getTransaction().commit(); em1.getTransaction().commit();
try { try {
em2.getTransaction().commit(); em2.getTransaction().commit();
fail("Expected optimistic exception on commit"); fail("Expected " + OptimisticLockException.class);
} catch (Throwable t) { } catch (Throwable t) {
if (!isExpectedException(t, OptimisticLockException.class)) { assertException(t, OptimisticLockException.class);
print(t); }
fail(t.getCause().getClass() + " is not " + }
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; * Asserts that the given expected type of the exception is equal to or a
if (expectedType.isAssignableFrom(t.getClass())) * subclass of the given throwable or any of its nested exception.
return true; * Otherwise fails assertion and prints the given throwable and its nested
if (t.getCause()==t) return false; * exception on the console.
return isExpectedException(t.getCause(), expectedType); */
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) { void print(Throwable t, int tab) {
if (t == null) return; if (t == null) return;
for (int i=0; i<tab*4;i++) System.out.print(" "); for (int i=0; i<tab*4;i++) System.out.print(" ");
String sqlState = (t instanceof SQLException) ? String sqlState = (t instanceof SQLException) ?
"(SQLState=" + ((SQLException)t).getSQLState() + ":" + t.getMessage() + ")":""; "(SQLState=" + ((SQLException)t).getSQLState() + ":"
+ t.getMessage() + ")" : "";
System.out.println(t.getClass().getName() + sqlState); System.out.println(t.getClass().getName() + sqlState);
if (t.getCause()==t) return; if (t.getCause() == t)
return;
print(t.getCause(), tab+1); print(t.getCause(), tab+1);
} }
} }

View File

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