You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2017/01/02 12:54:36 UTC
cayenne git commit: refactoring Transaction API towards cleaner
separation of responsibilities
Repository: cayenne
Updated Branches:
refs/heads/master cf67e0082 -> 36e70f4e7
refactoring Transaction API towards cleaner separation of responsibilities
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/36e70f4e
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/36e70f4e
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/36e70f4e
Branch: refs/heads/master
Commit: 36e70f4e72de3506f4ffeeeeeb6702ab3fbbba16
Parents: cf67e00
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Mon Jan 2 15:36:51 2017 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Mon Jan 2 15:50:49 2017 +0300
----------------------------------------------------------------------
.../org/apache/cayenne/access/DataNode.java | 196 ++++----
.../access/TransactionConnectionDecorator.java | 387 ----------------
.../org/apache/cayenne/tx/BaseTransaction.java | 443 ++++++++++---------
.../apache/cayenne/tx/CayenneTransaction.java | 11 +-
.../java/org/apache/cayenne/tx/Transaction.java | 29 +-
.../tx/TransactionConnectionDecorator.java | 386 ++++++++++++++++
.../cayenne/access/TransactionThreadIT.java | 117 -----
.../cayenne/access/UserTransactionIT.java | 114 -----
.../apache/cayenne/tx/TransactionThreadIT.java | 66 +++
.../apache/cayenne/tx/UserTransactionIT.java | 113 +++++
10 files changed, 912 insertions(+), 950 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
index 0014bdf..88b864b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
@@ -235,7 +235,7 @@ public class DataNode implements QueryEngine {
/**
* Returns a DataNode that should handle queries for all DataMap components.
- *
+ *
* @since 1.1
*/
public DataNode lookupDataNode(DataMap dataMap) {
@@ -245,7 +245,7 @@ public class DataNode implements QueryEngine {
/**
* Runs queries using Connection obtained from internal DataSource.
- *
+ *
* @since 1.1
*/
@Override
@@ -326,7 +326,7 @@ public class DataNode implements QueryEngine {
* Sets EntityResolver. DataNode relies on externally set EntityResolver, so
* if the node is created outside of DataDomain stack, a valid
* EntityResolver must be provided explicitly.
- *
+ *
* @since 1.1
*/
public void setEntityResolver(EntityResolver entityResolver) {
@@ -338,115 +338,10 @@ public class DataNode implements QueryEngine {
return new ToStringBuilder(this).append("name", getName()).toString();
}
- // a read-through DataSource that ensures returning the same connection
- // within
- // transaction.
- final class TransactionDataSource implements DataSource {
-
- final String CONNECTION_RESOURCE_PREFIX = "DataNode.Connection.";
-
- public Connection getConnection() throws SQLException {
- if (schemaUpdateStrategy != null) {
- schemaUpdateStrategy.updateSchema(DataNode.this);
- }
- Transaction t = BaseTransaction.getThreadTransaction();
-
- if (t != null) {
- String key = CONNECTION_RESOURCE_PREFIX + name;
- Connection c = t.getConnection(key);
-
- if (c == null || c.isClosed()) {
- c = dataSource.getConnection();
- t.addConnection(key, c);
- }
-
- // wrap transaction-attached connections in a decorator that
- // prevents them
- // from being closed by callers, as transaction should take care
- // of them
- // on commit or rollback.
- return new TransactionConnectionDecorator(c);
- }
-
- return dataSource.getConnection();
- }
-
- public Connection getConnection(String username, String password) throws SQLException {
- if (schemaUpdateStrategy != null) {
- schemaUpdateStrategy.updateSchema(DataNode.this);
- }
- Transaction t = BaseTransaction.getThreadTransaction();
- if (t != null) {
- String key = CONNECTION_RESOURCE_PREFIX + name;
- Connection c = t.getConnection(key);
-
- if (c == null || c.isClosed()) {
- c = dataSource.getConnection();
- t.addConnection(key, c);
- }
-
- // wrap transaction-attached connections in a decorator that
- // prevents them
- // from being closed by callers, as transaction should take care
- // of them
- // on commit or rollback.
- return new TransactionConnectionDecorator(c);
- }
-
- return dataSource.getConnection(username, password);
- }
-
- public int getLoginTimeout() throws SQLException {
- return dataSource.getLoginTimeout();
- }
-
- public PrintWriter getLogWriter() throws SQLException {
- return dataSource.getLogWriter();
- }
-
- public void setLoginTimeout(int seconds) throws SQLException {
- dataSource.setLoginTimeout(seconds);
- }
-
- public void setLogWriter(PrintWriter out) throws SQLException {
- dataSource.setLogWriter(out);
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public boolean isWrapperFor(Class<?> iface) throws SQLException {
- return iface.isAssignableFrom(dataSource.getClass());
- }
-
- /**
- * @since 3.0
- */
- // JDBC 4 compatibility under Java 1.5
- public <T> T unwrap(Class<T> iface) throws SQLException {
- try {
- return iface.cast(dataSource);
- } catch (ClassCastException e) {
- throw new SQLException("Not a DataSource: " + e.getMessage());
- }
- }
-
- /**
- * @since 3.1
- *
- * JDBC 4.1 compatibility under Java 1.5
- */
- public Logger getParentLogger() throws SQLFeatureNotSupportedException {
- // don't throw SQLFeatureNotSupported - this will break JDK 1.5
- // runtime
- throw new UnsupportedOperationException();
- }
- }
/**
* Creates a {@link RowReader} using internal {@link RowReaderFactory}.
- *
+ *
* @since 4.0
*/
public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) {
@@ -455,7 +350,7 @@ public class DataNode implements QueryEngine {
/**
* Creates a {@link RowReader} using internal {@link RowReaderFactory}.
- *
+ *
* @since 4.0
*/
public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
@@ -532,4 +427,85 @@ public class DataNode implements QueryEngine {
public void setSelectTranslatorFactory(SelectTranslatorFactory selectTranslatorFactory) {
this.selectTranslatorFactory = selectTranslatorFactory;
}
+
+ // a read-through DataSource that ensures returning the same connection
+ // within
+ // transaction.
+ final class TransactionDataSource implements DataSource {
+
+ final String CONNECTION_RESOURCE_PREFIX = "DataNode.Connection.";
+
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ if (schemaUpdateStrategy != null) {
+ schemaUpdateStrategy.updateSchema(DataNode.this);
+ }
+
+ Transaction t = BaseTransaction.getThreadTransaction();
+ return (t != null) ? t.getOrCreateConnection(CONNECTION_RESOURCE_PREFIX + name, dataSource)
+ : dataSource.getConnection();
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ if (schemaUpdateStrategy != null) {
+ schemaUpdateStrategy.updateSchema(DataNode.this);
+ }
+
+ Transaction t = BaseTransaction.getThreadTransaction();
+ return (t != null) ? t.getOrCreateConnection(CONNECTION_RESOURCE_PREFIX + name, dataSource)
+ : dataSource.getConnection(username, password);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return dataSource.getLoginTimeout();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return dataSource.getLogWriter();
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ dataSource.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ dataSource.setLogWriter(out);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return iface.isAssignableFrom(dataSource.getClass());
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ try {
+ return iface.cast(dataSource);
+ } catch (ClassCastException e) {
+ throw new SQLException("Not a DataSource: " + e.getMessage());
+ }
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ // don't throw SQLFeatureNotSupported - this will break JDK 1.5
+ // runtime
+ throw new UnsupportedOperationException();
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/access/TransactionConnectionDecorator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/TransactionConnectionDecorator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/TransactionConnectionDecorator.java
deleted file mode 100644
index 518a95e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/TransactionConnectionDecorator.java
+++ /dev/null
@@ -1,387 +0,0 @@
-/*****************************************************************
- * 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.cayenne.access;
-
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.CallableStatement;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.NClob;
-import java.sql.PreparedStatement;
-import java.sql.SQLClientInfoException;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.SQLXML;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.sql.Struct;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.Executor;
-
-/**
- * A wrapper of a JDBC connection that is attached to a transaction. The
- * behavior of this object to delegate all method calls to the underlying
- * connection, except for the 'close' method that is implemented as noop in hope
- * that a transaction originator will close the underlying Connection object.
- *
- * @since 1.2
- */
-class TransactionConnectionDecorator implements Connection {
-
- Connection connection;
-
- TransactionConnectionDecorator(Connection connection) {
- this.connection = connection;
- }
-
- // the only method that is NOT delegated...
- @Override
- public void close() throws SQLException {
- // noop
- }
-
- /**
- * @since 3.1
- */
- @Override
- public void abort(Executor executor) throws SQLException {
-
- // do nothing; same as 'close'
- }
-
- @Override
- public void clearWarnings() throws SQLException {
- connection.clearWarnings();
- }
-
- @Override
- public void commit() throws SQLException {
- connection.commit();
- }
-
- @Override
- public Statement createStatement() throws SQLException {
- return connection.createStatement();
- }
-
- @Override
- public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
- return connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
- }
-
- @Override
- public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
- return connection.createStatement(resultSetType, resultSetConcurrency);
- }
-
- @Override
- public boolean getAutoCommit() throws SQLException {
- return connection.getAutoCommit();
- }
-
- @Override
- public String getCatalog() throws SQLException {
- return connection.getCatalog();
- }
-
- @Override
- public int getHoldability() throws SQLException {
- return connection.getHoldability();
- }
-
- @Override
- public DatabaseMetaData getMetaData() throws SQLException {
- return connection.getMetaData();
- }
-
- @Override
- public int getTransactionIsolation() throws SQLException {
- return connection.getTransactionIsolation();
- }
-
- @Override
- public Map<String, Class<?>> getTypeMap() throws SQLException {
- return connection.getTypeMap();
- }
-
- @Override
- public SQLWarning getWarnings() throws SQLException {
- return connection.getWarnings();
- }
-
- @Override
- public boolean isClosed() throws SQLException {
- return connection.isClosed();
- }
-
- @Override
- public boolean isReadOnly() throws SQLException {
- return connection.isReadOnly();
- }
-
- @Override
- public String nativeSQL(String sql) throws SQLException {
- return connection.nativeSQL(sql);
- }
-
- @Override
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
- int resultSetHoldability) throws SQLException {
- return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
- }
-
- @Override
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
- return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
- }
-
- @Override
- public CallableStatement prepareCall(String sql) throws SQLException {
- return connection.prepareCall(sql);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
- int resultSetHoldability) throws SQLException {
- return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
- throws SQLException {
- return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
- return connection.prepareStatement(sql, autoGeneratedKeys);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
- return connection.prepareStatement(sql, columnIndexes);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
- return connection.prepareStatement(sql, columnNames);
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql) throws SQLException {
- return connection.prepareStatement(sql);
- }
-
- @Override
- public void releaseSavepoint(Savepoint savepoint) throws SQLException {
- connection.releaseSavepoint(savepoint);
- }
-
- @Override
- public void rollback() throws SQLException {
- connection.rollback();
- }
-
- @Override
- public void rollback(Savepoint savepoint) throws SQLException {
- connection.rollback(savepoint);
- }
-
- @Override
- public void setAutoCommit(boolean autoCommit) throws SQLException {
- connection.setAutoCommit(autoCommit);
- }
-
- @Override
- public void setCatalog(String catalog) throws SQLException {
- connection.setCatalog(catalog);
- }
-
- @Override
- public void setHoldability(int holdability) throws SQLException {
- connection.setHoldability(holdability);
- }
-
- @Override
- public void setReadOnly(boolean readOnly) throws SQLException {
- connection.setReadOnly(readOnly);
- }
-
- @Override
- public Savepoint setSavepoint() throws SQLException {
- return connection.setSavepoint();
- }
-
- @Override
- public Savepoint setSavepoint(String name) throws SQLException {
- return connection.setSavepoint(name);
- }
-
- @Override
- public void setTransactionIsolation(int level) throws SQLException {
- connection.setTransactionIsolation(level);
- }
-
- @Override
- public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
- connection.setTypeMap(map);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
- return connection.createArrayOf(typeName, elements);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public Blob createBlob() throws SQLException {
- return connection.createBlob();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public Clob createClob() throws SQLException {
- return connection.createClob();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
- return connection.createStruct(typeName, attributes);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public Properties getClientInfo() throws SQLException {
- return connection.getClientInfo();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public String getClientInfo(String name) throws SQLException {
- return connection.getClientInfo(name);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public void setClientInfo(Properties properties) throws SQLClientInfoException {
- connection.setClientInfo(properties);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public void setClientInfo(String name, String value) throws SQLClientInfoException {
- connection.setClientInfo(name, value);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public boolean isValid(int timeout) throws SQLException {
- return connection.isValid(timeout);
- }
-
- /**
- * @since 3.0
- */
- @Override
- public boolean isWrapperFor(Class<?> iface) throws SQLException {
- // TODO...
- throw new UnsupportedOperationException();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public <T> T unwrap(Class<T> iface) throws SQLException {
- // TODO
- throw new UnsupportedOperationException();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public NClob createNClob() throws SQLException {
- return connection.createNClob();
- }
-
- /**
- * @since 3.0
- */
- @Override
- public SQLXML createSQLXML() throws SQLException {
- return connection.createSQLXML();
- }
-
- /**
- * @since 3.1
- */
- @Override
- public void setSchema(String schema) throws SQLException {
- connection.setSchema(schema);
- }
-
- /**
- * @since 3.1
- */
- @Override
- public String getSchema() throws SQLException {
- return connection.getSchema();
- }
-
- /**
- * @since 3.1
- */
- @Override
- public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
- connection.setNetworkTimeout(executor, milliseconds);
- }
-
- /**
- * @since 3.1
- */
- public int getNetworkTimeout() throws SQLException {
- return connection.getNetworkTimeout();
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
index 8ed44fc..6281b01 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/BaseTransaction.java
@@ -19,226 +19,249 @@
package org.apache.cayenne.tx;
+import javax.sql.DataSource;
import java.sql.Connection;
+import java.sql.SQLException;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
/**
* A Cayenne transaction. Currently supports managing JDBC connections.
- *
+ *
* @since 4.0
*/
public abstract class BaseTransaction implements Transaction {
- /**
- * A ThreadLocal that stores current thread transaction.
- */
- static final ThreadLocal<Transaction> currentTransaction = new InheritableThreadLocal<Transaction>();
-
- protected static final int STATUS_ACTIVE = 1;
- protected static final int STATUS_COMMITTING = 2;
- protected static final int STATUS_COMMITTED = 3;
- protected static final int STATUS_ROLLEDBACK = 4;
- protected static final int STATUS_ROLLING_BACK = 5;
- protected static final int STATUS_NO_TRANSACTION = 6;
- protected static final int STATUS_MARKED_ROLLEDBACK = 7;
-
- protected Map<String, Connection> connections;
- protected Collection<TransactionListener> listeners;
- protected int status;
-
- static String decodeStatus(int status) {
- switch (status) {
- case STATUS_ACTIVE:
- return "STATUS_ACTIVE";
- case STATUS_COMMITTING:
- return "STATUS_COMMITTING";
- case STATUS_COMMITTED:
- return "STATUS_COMMITTED";
- case STATUS_ROLLEDBACK:
- return "STATUS_ROLLEDBACK";
- case STATUS_ROLLING_BACK:
- return "STATUS_ROLLING_BACK";
- case STATUS_NO_TRANSACTION:
- return "STATUS_NO_TRANSACTION";
- case STATUS_MARKED_ROLLEDBACK:
- return "STATUS_MARKED_ROLLEDBACK";
- default:
- return "Unknown Status - " + status;
- }
- }
-
- /**
- * Binds a Transaction to the current thread.
- */
- public static void bindThreadTransaction(Transaction transaction) {
- currentTransaction.set(transaction);
- }
-
- /**
- * Returns a Transaction associated with the current thread, or null if
- * there is no such Transaction.
- */
- public static Transaction getThreadTransaction() {
- return currentTransaction.get();
- }
-
- /**
- * Creates new inactive transaction.
- */
- protected BaseTransaction() {
- this.status = STATUS_NO_TRANSACTION;
- }
-
- @Override
- public void setRollbackOnly() {
- this.status = STATUS_MARKED_ROLLEDBACK;
- }
-
- @Override
- public boolean isRollbackOnly() {
- return status == STATUS_MARKED_ROLLEDBACK;
- }
-
- @Override
- public void addListener(TransactionListener listener) {
- if(listeners == null) {
- listeners = new LinkedHashSet<>();
- }
-
- listeners.add(listener);
- }
-
- /**
- * Starts a Transaction. If Transaction is not started explicitly, it will
- * be started when the first connection is added.
- */
- @Override
- public void begin() {
- if (status != BaseTransaction.STATUS_NO_TRANSACTION) {
- throw new IllegalStateException("Transaction must have 'STATUS_NO_TRANSACTION' to begin. "
- + "Current status: " + BaseTransaction.decodeStatus(status));
- }
-
- status = BaseTransaction.STATUS_ACTIVE;
- }
-
- @Override
- public void commit() {
-
- if (status == BaseTransaction.STATUS_NO_TRANSACTION) {
- return;
- }
-
- if (status != BaseTransaction.STATUS_ACTIVE) {
- throw new IllegalStateException("Transaction must have 'STATUS_ACTIVE' to be committed. "
- + "Current status: " + BaseTransaction.decodeStatus(status));
- }
-
- if(listeners != null) {
- for(TransactionListener listener : listeners) {
- listener.willCommit(this);
- }
- }
-
- processCommit();
-
- status = BaseTransaction.STATUS_COMMITTED;
-
- close();
- }
-
- protected abstract void processCommit();
-
- @Override
- public void rollback() {
-
- try {
-
- if (status == BaseTransaction.STATUS_NO_TRANSACTION || status == BaseTransaction.STATUS_ROLLEDBACK
- || status == BaseTransaction.STATUS_ROLLING_BACK) {
- return;
- }
-
- if (status != BaseTransaction.STATUS_ACTIVE && status != BaseTransaction.STATUS_MARKED_ROLLEDBACK) {
- throw new IllegalStateException(
- "Transaction must have 'STATUS_ACTIVE' or 'STATUS_MARKED_ROLLEDBACK' to be rolled back. "
- + "Current status: " + BaseTransaction.decodeStatus(status));
- }
-
- if(listeners != null) {
- for(TransactionListener listener : listeners) {
- listener.willRollback(this);
- }
- }
-
- processRollback();
-
- status = BaseTransaction.STATUS_ROLLEDBACK;
-
- } finally {
- close();
- }
- }
-
- protected abstract void processRollback();
-
- @Override
- public Connection getConnection(String name) {
- return (connections != null) ? connections.get(name) : null;
- }
-
- @Override
- public void addConnection(String connectionName, Connection connection) {
-
- if(listeners != null) {
- for(TransactionListener listener : listeners) {
- listener.willAddConnection(this, connectionName, connection);
- }
- }
-
- if (connections == null) {
- connections = new HashMap<>();
- }
-
- if (connections.put(connectionName, connection) != connection) {
- connectionAdded(connection);
- }
- }
-
- protected void connectionAdded(Connection connection) {
-
- // implicitly begin transaction
- if (status == BaseTransaction.STATUS_NO_TRANSACTION) {
- begin();
- }
-
- if (status != BaseTransaction.STATUS_ACTIVE) {
- throw new IllegalStateException("Transaction must have 'STATUS_ACTIVE' to add a connection. "
- + "Current status: " + BaseTransaction.decodeStatus(status));
- }
- }
-
- /**
- * Closes all connections associated with transaction.
- */
- protected void close() {
- if (connections == null || connections.isEmpty()) {
- return;
- }
-
- Iterator<?> it = connections.values().iterator();
- while (it.hasNext()) {
- try {
-
- ((Connection) it.next()).close();
- } catch (Throwable th) {
- // TODO: chain exceptions...
- // ignore for now
- }
- }
- }
+ /**
+ * A ThreadLocal that stores current thread transaction.
+ */
+ static final ThreadLocal<Transaction> CURRENT_TRANSACTION = new InheritableThreadLocal<>();
+
+ protected static final int STATUS_ACTIVE = 1;
+ protected static final int STATUS_COMMITTING = 2;
+ protected static final int STATUS_COMMITTED = 3;
+ protected static final int STATUS_ROLLEDBACK = 4;
+ protected static final int STATUS_ROLLING_BACK = 5;
+ protected static final int STATUS_NO_TRANSACTION = 6;
+ protected static final int STATUS_MARKED_ROLLEDBACK = 7;
+
+ protected Map<String, Connection> connections;
+ protected Collection<TransactionListener> listeners;
+ protected int status;
+
+ static String decodeStatus(int status) {
+ switch (status) {
+ case STATUS_ACTIVE:
+ return "STATUS_ACTIVE";
+ case STATUS_COMMITTING:
+ return "STATUS_COMMITTING";
+ case STATUS_COMMITTED:
+ return "STATUS_COMMITTED";
+ case STATUS_ROLLEDBACK:
+ return "STATUS_ROLLEDBACK";
+ case STATUS_ROLLING_BACK:
+ return "STATUS_ROLLING_BACK";
+ case STATUS_NO_TRANSACTION:
+ return "STATUS_NO_TRANSACTION";
+ case STATUS_MARKED_ROLLEDBACK:
+ return "STATUS_MARKED_ROLLEDBACK";
+ default:
+ return "Unknown Status - " + status;
+ }
+ }
+
+ /**
+ * Binds a Transaction to the current thread.
+ */
+ public static void bindThreadTransaction(Transaction transaction) {
+ CURRENT_TRANSACTION.set(transaction);
+ }
+
+ /**
+ * Returns a Transaction associated with the current thread, or null if
+ * there is no such Transaction.
+ */
+ public static Transaction getThreadTransaction() {
+ return CURRENT_TRANSACTION.get();
+ }
+
+ /**
+ * Creates new inactive transaction.
+ */
+ protected BaseTransaction() {
+ this.status = STATUS_NO_TRANSACTION;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ this.status = STATUS_MARKED_ROLLEDBACK;
+ }
+
+ @Override
+ public boolean isRollbackOnly() {
+ return status == STATUS_MARKED_ROLLEDBACK;
+ }
+
+ @Override
+ public void addListener(TransactionListener listener) {
+ if (listeners == null) {
+ listeners = new LinkedHashSet<>();
+ }
+
+ listeners.add(listener);
+ }
+
+ /**
+ * Starts a Transaction. If Transaction is not started explicitly, it will
+ * be started when the first connection is added.
+ */
+ @Override
+ public void begin() {
+ if (status != BaseTransaction.STATUS_NO_TRANSACTION) {
+ throw new IllegalStateException("Transaction must have 'STATUS_NO_TRANSACTION' to begin. "
+ + "Current status: " + BaseTransaction.decodeStatus(status));
+ }
+
+ status = BaseTransaction.STATUS_ACTIVE;
+ }
+
+ @Override
+ public void commit() {
+
+ if (status == BaseTransaction.STATUS_NO_TRANSACTION) {
+ return;
+ }
+
+ if (status != BaseTransaction.STATUS_ACTIVE) {
+ throw new IllegalStateException("Transaction must have 'STATUS_ACTIVE' to be committed. "
+ + "Current status: " + BaseTransaction.decodeStatus(status));
+ }
+
+ if (listeners != null) {
+ for (TransactionListener listener : listeners) {
+ listener.willCommit(this);
+ }
+ }
+
+ processCommit();
+
+ status = BaseTransaction.STATUS_COMMITTED;
+
+ close();
+ }
+
+ protected abstract void processCommit();
+
+ @Override
+ public void rollback() {
+
+ try {
+
+ if (status == BaseTransaction.STATUS_NO_TRANSACTION || status == BaseTransaction.STATUS_ROLLEDBACK
+ || status == BaseTransaction.STATUS_ROLLING_BACK) {
+ return;
+ }
+
+ if (status != BaseTransaction.STATUS_ACTIVE && status != BaseTransaction.STATUS_MARKED_ROLLEDBACK) {
+ throw new IllegalStateException(
+ "Transaction must have 'STATUS_ACTIVE' or 'STATUS_MARKED_ROLLEDBACK' to be rolled back. "
+ + "Current status: " + BaseTransaction.decodeStatus(status));
+ }
+
+ if (listeners != null) {
+ for (TransactionListener listener : listeners) {
+ listener.willRollback(this);
+ }
+ }
+
+ processRollback();
+
+ status = BaseTransaction.STATUS_ROLLEDBACK;
+
+ } finally {
+ close();
+ }
+ }
+
+ protected abstract void processRollback();
+
+ @Override
+ public Map<String, Connection> getConnections() {
+ return connections != null ? Collections.unmodifiableMap(connections) : Collections.<String, Connection>emptyMap();
+ }
+
+ @Override
+ public Connection getOrCreateConnection(String connectionName, DataSource dataSource) throws SQLException {
+
+ Connection c = getExistingConnection(connectionName);
+
+ if (c == null || c.isClosed()) {
+ c = dataSource.getConnection();
+ addConnection(connectionName, c);
+ }
+
+ // wrap transaction-attached connections in a decorator that prevents them from being closed by callers, as
+ // transaction should take care of them on commit or rollback.
+ return new TransactionConnectionDecorator(c);
+ }
+
+ protected Connection getExistingConnection(String name) {
+ return (connections != null) ? connections.get(name) : null;
+ }
+
+ protected Connection addConnection(String connectionName, Connection connection) {
+
+ TransactionConnectionDecorator wrapper = new TransactionConnectionDecorator(connection);
+
+ if (listeners != null) {
+ for (TransactionListener listener : listeners) {
+ listener.willAddConnection(this, connectionName, wrapper);
+ }
+ }
+
+ if (connections == null) {
+ connections = new HashMap<>();
+ }
+
+ if (connections.put(connectionName, wrapper) != wrapper) {
+ connectionAdded(connection);
+ }
+
+ return wrapper;
+ }
+
+ protected void connectionAdded(Connection connection) {
+
+ // implicitly begin transaction
+ if (status == BaseTransaction.STATUS_NO_TRANSACTION) {
+ begin();
+ }
+
+ if (status != BaseTransaction.STATUS_ACTIVE) {
+ throw new IllegalStateException("Transaction must have 'STATUS_ACTIVE' to add a connection. "
+ + "Current status: " + BaseTransaction.decodeStatus(status));
+ }
+ }
+
+ /**
+ * Closes all connections associated with transaction.
+ */
+ protected void close() {
+ if (connections == null || connections.isEmpty()) {
+ return;
+ }
+
+ for (Connection c : connections.values()) {
+ try {
+ // make sure we unwrap TX connection before closing it, as the TX wrapper's "close" does nothing.
+ c.unwrap(Connection.class).close();
+ } catch (Throwable th) {
+ // TODO: chain exceptions...
+ // ignore for now
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
index 9c69db2..f0ab5d8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/CayenneTransaction.java
@@ -19,13 +19,13 @@
package org.apache.cayenne.tx;
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.log.JdbcEventLogger;
+
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.log.JdbcEventLogger;
-
/**
* Represents a Cayenne-managed local Transaction.
*
@@ -58,9 +58,8 @@ public class CayenneTransaction extends BaseTransaction {
void fixConnectionState(Connection connection) throws SQLException {
if (connection.getAutoCommit()) {
- // some DBs are very particular about that, (e.g. Informix SE 7.0
- // per
- // CAY-179), so do a try-catch and ignore exception
+ // some DBs are very particular about that, (e.g. Informix SE 7.0 per CAY-179), so do a try-catch and
+ // ignore exception
// TODO: maybe allow adapter to provide transaction instance?
try {
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/tx/Transaction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/Transaction.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/Transaction.java
index 789573f..f87e8c3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/tx/Transaction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/Transaction.java
@@ -18,18 +18,21 @@
****************************************************************/
package org.apache.cayenne.tx;
+import javax.sql.DataSource;
import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
/**
- * A Cayenne Transaction interface.
- *
+ * Cayenne Transaction interface.
+ *
* @since 4.0
*/
public interface Transaction {
/**
- * Starts a Transaction. If Transaction is not started explicitly, it will
- * be started when the first connection is added.
+ * Starts a Transaction. If Transaction is not started explicitly, it will be started when the first connection is
+ * added.
*/
void begin();
@@ -41,9 +44,23 @@ public interface Transaction {
boolean isRollbackOnly();
- Connection getConnection(String name);
+ /**
+ * Retrieves a connection for the given symbolic name. If it does not exists, creates a new connection using
+ * provided DataSource, and registers it internally.
+ *
+ * @param connectionName a symbolic name of the connection. Cayenne DataNodes generate a name in the form of
+ * "DataNode.Connection.nodename".
+ * @param dataSource DataSource that provides new connections.
+ * @return a connection that participates in the current transaction.
+ */
+ Connection getOrCreateConnection(String connectionName, DataSource dataSource) throws SQLException;
- void addConnection(String connectionName, Connection connection);
+ /**
+ * Returns all connections associated with the transaction.
+ *
+ * @return connections associated with the transaction.
+ */
+ Map<String, Connection> getConnections();
void addListener(TransactionListener listener);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionConnectionDecorator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionConnectionDecorator.java b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionConnectionDecorator.java
new file mode 100644
index 0000000..32d3f0c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/tx/TransactionConnectionDecorator.java
@@ -0,0 +1,386 @@
+/*****************************************************************
+ * 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.cayenne.tx;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+/**
+ * A wrapper of a JDBC connection that is attached to a transaction. The behavior of this object to delegate all method
+ * calls to the underlying connection, except for the 'close' method that is implemented as noop in hope that a
+ * transaction originator will close the underlying Connection object.
+ */
+class TransactionConnectionDecorator implements Connection {
+
+ Connection connection;
+
+ TransactionConnectionDecorator(Connection connection) {
+ this.connection = connection;
+ }
+
+ // the only method that is NOT delegated...
+ @Override
+ public void close() throws SQLException {
+ // noop
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public void abort(Executor executor) throws SQLException {
+
+ // do nothing; same as 'close'
+ }
+
+ @Override
+ public void clearWarnings() throws SQLException {
+ connection.clearWarnings();
+ }
+
+ @Override
+ public void commit() throws SQLException {
+ connection.commit();
+ }
+
+ @Override
+ public Statement createStatement() throws SQLException {
+ return connection.createStatement();
+ }
+
+ @Override
+ public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+ throws SQLException {
+ return connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ @Override
+ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+ return connection.createStatement(resultSetType, resultSetConcurrency);
+ }
+
+ @Override
+ public boolean getAutoCommit() throws SQLException {
+ return connection.getAutoCommit();
+ }
+
+ @Override
+ public String getCatalog() throws SQLException {
+ return connection.getCatalog();
+ }
+
+ @Override
+ public int getHoldability() throws SQLException {
+ return connection.getHoldability();
+ }
+
+ @Override
+ public DatabaseMetaData getMetaData() throws SQLException {
+ return connection.getMetaData();
+ }
+
+ @Override
+ public int getTransactionIsolation() throws SQLException {
+ return connection.getTransactionIsolation();
+ }
+
+ @Override
+ public Map<String, Class<?>> getTypeMap() throws SQLException {
+ return connection.getTypeMap();
+ }
+
+ @Override
+ public SQLWarning getWarnings() throws SQLException {
+ return connection.getWarnings();
+ }
+
+ @Override
+ public boolean isClosed() throws SQLException {
+ return connection.isClosed();
+ }
+
+ @Override
+ public boolean isReadOnly() throws SQLException {
+ return connection.isReadOnly();
+ }
+
+ @Override
+ public String nativeSQL(String sql) throws SQLException {
+ return connection.nativeSQL(sql);
+ }
+
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+ return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
+ }
+
+ @Override
+ public CallableStatement prepareCall(String sql) throws SQLException {
+ return connection.prepareCall(sql);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+ throws SQLException {
+ return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+ return connection.prepareStatement(sql, autoGeneratedKeys);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+ return connection.prepareStatement(sql, columnIndexes);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+ return connection.prepareStatement(sql, columnNames);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql) throws SQLException {
+ return connection.prepareStatement(sql);
+ }
+
+ @Override
+ public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+ connection.releaseSavepoint(savepoint);
+ }
+
+ @Override
+ public void rollback() throws SQLException {
+ connection.rollback();
+ }
+
+ @Override
+ public void rollback(Savepoint savepoint) throws SQLException {
+ connection.rollback(savepoint);
+ }
+
+ @Override
+ public void setAutoCommit(boolean autoCommit) throws SQLException {
+ connection.setAutoCommit(autoCommit);
+ }
+
+ @Override
+ public void setCatalog(String catalog) throws SQLException {
+ connection.setCatalog(catalog);
+ }
+
+ @Override
+ public void setHoldability(int holdability) throws SQLException {
+ connection.setHoldability(holdability);
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) throws SQLException {
+ connection.setReadOnly(readOnly);
+ }
+
+ @Override
+ public Savepoint setSavepoint() throws SQLException {
+ return connection.setSavepoint();
+ }
+
+ @Override
+ public Savepoint setSavepoint(String name) throws SQLException {
+ return connection.setSavepoint(name);
+ }
+
+ @Override
+ public void setTransactionIsolation(int level) throws SQLException {
+ connection.setTransactionIsolation(level);
+ }
+
+ @Override
+ public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+ connection.setTypeMap(map);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+ return connection.createArrayOf(typeName, elements);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public Blob createBlob() throws SQLException {
+ return connection.createBlob();
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public Clob createClob() throws SQLException {
+ return connection.createClob();
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+ return connection.createStruct(typeName, attributes);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public Properties getClientInfo() throws SQLException {
+ return connection.getClientInfo();
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public String getClientInfo(String name) throws SQLException {
+ return connection.getClientInfo(name);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public void setClientInfo(Properties properties) throws SQLClientInfoException {
+ connection.setClientInfo(properties);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public void setClientInfo(String name, String value) throws SQLClientInfoException {
+ connection.setClientInfo(name, value);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public boolean isValid(int timeout) throws SQLException {
+ return connection.isValid(timeout);
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ return iface.isAssignableFrom(connection.getClass());
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ try {
+ return iface.cast(connection);
+ } catch (ClassCastException e) {
+ throw new SQLException("Not a Connection: " + e.getMessage());
+ }
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public NClob createNClob() throws SQLException {
+ return connection.createNClob();
+ }
+
+ /**
+ * @since 3.0
+ */
+ @Override
+ public SQLXML createSQLXML() throws SQLException {
+ return connection.createSQLXML();
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public void setSchema(String schema) throws SQLException {
+ connection.setSchema(schema);
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public String getSchema() throws SQLException {
+ return connection.getSchema();
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
+ connection.setNetworkTimeout(executor, milliseconds);
+ }
+
+ /**
+ * @since 3.1
+ */
+ public int getNetworkTimeout() throws SQLException {
+ return connection.getNetworkTimeout();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/test/java/org/apache/cayenne/access/TransactionThreadIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/TransactionThreadIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/TransactionThreadIT.java
deleted file mode 100644
index 50368e2..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/TransactionThreadIT.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*****************************************************************
- * 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.cayenne.access;
-
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.tx.BaseTransaction;
-import org.apache.cayenne.tx.CayenneTransaction;
-import org.apache.cayenne.tx.Transaction;
-import org.apache.cayenne.tx.TransactionListener;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-import java.sql.Connection;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class TransactionThreadIT extends ServerCase {
-
- @Inject
- private DataContext context;
-
- @Inject
- private JdbcEventLogger logger;
-
- @Test
- public void testThreadConnectionReuseOnSelect() throws Exception {
-
- ConnectionCounterTx t = new ConnectionCounterTx(new CayenneTransaction(logger));
- BaseTransaction.bindThreadTransaction(t);
-
- try {
-
- SelectQuery q1 = new SelectQuery(Artist.class);
- context.performQuery(q1);
- assertEquals(1, t.connectionCount);
-
- // delegate will fail if the second query opens a new connection
- SelectQuery q2 = new SelectQuery(Artist.class);
- context.performQuery(q2);
-
- } finally {
- BaseTransaction.bindThreadTransaction(null);
- t.commit();
- }
- }
-
- class ConnectionCounterTx implements Transaction {
-
- private Transaction delegate;
- int connectionCount;
-
- ConnectionCounterTx(Transaction delegate) {
- this.delegate = delegate;
- }
-
- public void begin() {
- delegate.begin();
- }
-
- public void commit() {
- delegate.commit();
- }
-
- public void rollback() {
- delegate.rollback();
- }
-
- public void setRollbackOnly() {
- delegate.setRollbackOnly();
- }
-
- public boolean isRollbackOnly() {
- return delegate.isRollbackOnly();
- }
-
- public Connection getConnection(String name) {
- return delegate.getConnection(name);
- }
-
- public void addConnection(String name, Connection connection) {
- if (connectionCount++ > 0) {
- fail("Invalid attempt to add connection");
- }
-
- delegate.addConnection(name, connection);
- }
-
- @Override
- public void addListener(TransactionListener listener) {
- delegate.addListener(listener);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/test/java/org/apache/cayenne/access/UserTransactionIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/UserTransactionIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/UserTransactionIT.java
deleted file mode 100644
index f644639..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/UserTransactionIT.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*****************************************************************
- * 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.cayenne.access;
-
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.tx.BaseTransaction;
-import org.apache.cayenne.tx.CayenneTransaction;
-import org.apache.cayenne.tx.Transaction;
-import org.apache.cayenne.tx.TransactionListener;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-import java.sql.Connection;
-
-import static org.junit.Assert.assertEquals;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class UserTransactionIT extends ServerCase {
-
- @Inject
- private ObjectContext context;
-
- @Inject
- private JdbcEventLogger logger;
-
- @Test
- public void testCommit() throws Exception {
-
- Artist a = context.newObject(Artist.class);
- a.setArtistName("AAA");
-
- TxWrapper t = new TxWrapper(new CayenneTransaction(logger));
- BaseTransaction.bindThreadTransaction(t);
-
- try {
- context.commitChanges();
- } finally {
- t.rollback();
- BaseTransaction.bindThreadTransaction(null);
- }
-
- assertEquals(0, t.commitCount);
- assertEquals(1, t.connectionCount);
- }
-
- class TxWrapper implements Transaction {
-
- int commitCount;
- int connectionCount;
- private Transaction delegate;
-
- TxWrapper(Transaction delegate) {
- this.delegate = delegate;
- }
-
- public void begin() {
- delegate.begin();
- }
-
- public void commit() {
- commitCount++;
- delegate.commit();
- }
-
- public void rollback() {
- delegate.rollback();
- }
-
- public void setRollbackOnly() {
- delegate.setRollbackOnly();
- }
-
- public boolean isRollbackOnly() {
- return delegate.isRollbackOnly();
- }
-
- public Connection getConnection(String name) {
- return delegate.getConnection(name);
- }
-
- public void addConnection(String name, Connection connection) {
- connectionCount++;
- delegate.addConnection(name, connection);
- }
-
- @Override
- public void addListener(TransactionListener listener) {
- delegate.addListener(listener);
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionThreadIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionThreadIT.java b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionThreadIT.java
new file mode 100644
index 0000000..f2ef5ac
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/TransactionThreadIT.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ * 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.cayenne.tx;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class TransactionThreadIT extends ServerCase {
+
+ @Inject
+ private DataContext context;
+
+ @Inject
+ private JdbcEventLogger logger;
+
+ @Test
+ public void testThreadConnectionReuseOnSelect() throws Exception {
+
+ Transaction t = new CayenneTransaction(logger);
+ BaseTransaction.bindThreadTransaction(t);
+
+ try {
+
+ SelectQuery q1 = new SelectQuery(Artist.class);
+ context.performQuery(q1);
+ assertEquals(1, t.getConnections().size());
+
+ // delegate will fail if the second query opens a new connection
+ SelectQuery q2 = new SelectQuery(Artist.class);
+ context.performQuery(q2);
+
+ assertEquals(1, t.getConnections().size());
+
+ } finally {
+ BaseTransaction.bindThreadTransaction(null);
+ t.commit();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/36e70f4e/cayenne-server/src/test/java/org/apache/cayenne/tx/UserTransactionIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/tx/UserTransactionIT.java b/cayenne-server/src/test/java/org/apache/cayenne/tx/UserTransactionIT.java
new file mode 100644
index 0000000..9d1338f
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/tx/UserTransactionIT.java
@@ -0,0 +1,113 @@
+/*****************************************************************
+ * 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.cayenne.tx;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class UserTransactionIT extends ServerCase {
+
+ @Inject
+ private ObjectContext context;
+
+ @Inject
+ private JdbcEventLogger logger;
+
+ @Test
+ public void testCommit() throws Exception {
+
+ Artist a = context.newObject(Artist.class);
+ a.setArtistName("AAA");
+
+ TxWrapper t = new TxWrapper(new CayenneTransaction(logger));
+ BaseTransaction.bindThreadTransaction(t);
+
+ try {
+ context.commitChanges();
+ } finally {
+ t.rollback();
+ BaseTransaction.bindThreadTransaction(null);
+ }
+
+ assertEquals(0, t.commitCount);
+ assertEquals(1, t.getConnections().size());
+ }
+
+ class TxWrapper implements Transaction {
+
+ int commitCount;
+ private Transaction delegate;
+
+ TxWrapper(Transaction delegate) {
+ this.delegate = delegate;
+ }
+
+ public void begin() {
+ delegate.begin();
+ }
+
+ public void commit() {
+ commitCount++;
+ delegate.commit();
+ }
+
+ public void rollback() {
+ delegate.rollback();
+ }
+
+ public void setRollbackOnly() {
+ delegate.setRollbackOnly();
+ }
+
+ public boolean isRollbackOnly() {
+ return delegate.isRollbackOnly();
+ }
+
+ @Override
+ public Connection getOrCreateConnection(String connectionName, DataSource dataSource) throws SQLException {
+ return delegate.getOrCreateConnection(connectionName, dataSource);
+ }
+
+ @Override
+ public Map<String, Connection> getConnections() {
+ return delegate.getConnections();
+ }
+
+ @Override
+ public void addListener(TransactionListener listener) {
+ delegate.addListener(listener);
+ }
+ }
+
+}