mirror of https://github.com/apache/openjpa.git
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:
parent
e00c6e8f80
commit
a8e0dc180e
|
@ -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,6 +4107,30 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
@ -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,24 +4201,34 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -18,16 +18,31 @@
|
|||
*/
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (t == null)
|
||||
return false;
|
||||
if (expectedType.isAssignableFrom(t.getClass()))
|
||||
return true;
|
||||
if (t.getCause()==t) return false;
|
||||
return isExpectedException(t.getCause(), expectedType);
|
||||
}
|
||||
|
||||
void print(Throwable t) {
|
||||
print(t, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
} 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());
|
||||
|
|
Loading…
Reference in New Issue