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.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.
|
||||||
|
|
|
@ -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()]);
|
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.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.
|
||||||
|
|
|
@ -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
|
@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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue