/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ package org.geotools.arcsde.data; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.arcsde.session.Command; import org.geotools.arcsde.session.ISession; import org.geotools.arcsde.session.ISessionPool; import org.geotools.arcsde.session.SessionWrapper; import org.geotools.arcsde.session.UnavailableConnectionException; import org.geotools.data.DataSourceException; import org.geotools.data.Transaction; import org.geotools.data.Transaction.State; import com.esri.sde.sdk.client.SeConnection; import com.esri.sde.sdk.client.SeException; /** * Store the transaction state needed for a <code>Session</code> instances. * <p> * This transaction state is used to hold the SeConnection needed for a Session. * </p> * * @author Jake Fear * @author Gabriel Roldan * @source $URL: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/datastore/src/main/java * /org/geotools/arcsde/data/ArcTransactionState.java $ * @version $Id$ */ final class SessionTransactionState implements Transaction.State { private static final Logger LOGGER = org.geotools.util.logging.Logging .getLogger(SessionTransactionState.class.getPackage().getName()); /** * The session being managed, it will be held open until commit(), rollback() or close() is * called. */ private TransactionSession session; /** * The transaction that is holding on to this Transaction.State */ private Transaction transaction; /** * Creates a new ArcTransactionState object. * * @param listenerManager * @param pool * connection pool where to grab a connection and hold it while there's a transaction * open (signaled by any use of {@link #getConnection()} */ private SessionTransactionState(final ISession session) { if (!session.isTransactionActive()) { throw new IllegalArgumentException("session shall be in transactional mode"); } this.session = new TransactionSession(session); } /** * Commits the transaction and returns the connection to the pool. A new one will be grabbed * when needed. * <p> * Preconditions: * <ul> * <li>{@link #setTransaction(Transaction)} already called with non <code>null</code> argument. * <li> * </ul> * </p> */ public void commit() throws IOException { failIfClosed(); final ISession session = this.session; final Command<Void> commitCommand = new Command<Void>() { @Override public Void execute(ISession session, SeConnection connection) throws SeException, IOException { try { session.commitTransaction(); session.startTransaction(); } catch (IOException se) { LOGGER.log(Level.WARNING, se.getMessage(), se); throw se; } return null; } }; try { session.issue(commitCommand); } catch (IOException e) { throw e; } } /** * */ public void rollback() throws IOException { failIfClosed(); final ISession session = this.session; try { session.issue(new Command<Void>() { @Override public Void execute(ISession session, SeConnection connection) throws SeException, IOException { session.rollbackTransaction(); // and keep editing session.startTransaction(); return null; } }); } catch (IOException se) { close(); LOGGER.log(Level.WARNING, se.getMessage(), se); throw se; } } /** * @see State#addAuthorization(String) */ public void addAuthorization(String authId) { // intentionally blank we are not making use of ArcSDE locking } /** * Transaction start/end. * <p> * If the provided transaction is non null we are being added to the Transaction. If the * provided transaction is null we are being shutdown. * </p> * * @see Transaction.State#setTransaction(Transaction) * @param transaction * transaction information, <code>null</code> signals this state lifecycle end. * @throws IllegalStateException * if close() is called while a transaction is in progress */ public void setTransaction(final Transaction transaction) { if (Transaction.AUTO_COMMIT.equals(transaction)) { throw new IllegalArgumentException("Cannot use Transaction.AUTO_COMMIT here"); } if (transaction == null) { // this is a call to free resources (ugly, but that's what the API says) close(); } else if (this.transaction != null) { // assert this assumption throw new IllegalStateException( "Once a transaction is set, it is " + "illegal to call Transaction.State.setTransaction with anything other than null: " + transaction); } this.transaction = transaction; } /** * If this state has been closed throws an unchecked exception as its clearly a broken workflow. * * @throws IllegalStateException * if the transaction state has been closed. */ private void failIfClosed() throws IllegalStateException { if (session == null) { throw new IllegalStateException("This transaction state has already been closed"); } } /** * Releases resources and invalidates this state (signaled by setting the connection to null) */ private void close() { if (session == null) { return; } // can't even try to use this state in any way from now on // may throw ISE if transaction is still in progress try { // release current transaction before returning the // connection to the pool try { session.rollbackTransaction(); // connection.setConcurrency(SeConnection.SE_UNPROTECTED_POLICY); } catch (IOException e) { // TODO: this shouldn't happen, but if it does // we should somehow invalidate the connection? LOGGER.log(Level.SEVERE, "Unexpected exception at close(): " + e.getMessage(), e); } // now its safe to return it to the pool session.dispose(); } catch (IllegalStateException workflowError) { // fail fast but put the connection in a healthy state first try { session.rollbackTransaction(); } catch (IOException e) { // well, it's totally messed up, just log though LOGGER.log(Level.SEVERE, "rolling back connection " + session, e); session.dispose(); } throw workflowError; } finally { session = null; } } /** * Used only within the package to provide access to a single connection on which this * transaction is being conducted. * * @return the session tied to ths state's transaction */ ISession getConnection() { failIfClosed(); return session; } public Transaction getTransaction() { return transaction; } /** * Grab the SessionTransactionState (when not using AUTO_COMMIT). * <p> * As of GeoTools 2.5 we store the TransactionState using the connection pool as a key. * </p> * * @return the SessionTransactionState stored in the transaction with * <code>connectionPool</code> as key. */ public static SessionTransactionState getState(final Transaction transaction, final ISessionPool pool) throws IOException { SessionTransactionState state; if (transaction == Transaction.AUTO_COMMIT) { LOGGER.log(Level.SEVERE, "Should not request ArcTransactionState when using AUTO_COMMITback connection"); return null; } synchronized (SessionTransactionState.class) { state = (SessionTransactionState) transaction.getState(pool); if (state == null) { // start a transaction ISession session; try { session = pool.getSession(true); } catch (UnavailableConnectionException e) { throw new RuntimeException( "Can't create a transaction state, connection pool exhausted", e); } try { session.startTransaction(); } catch (IOException e) { try { session.rollbackTransaction(); } finally { session.dispose(); } throw new DataSourceException("Exception initiating transaction on " + session, e); } state = new SessionTransactionState(session); transaction.putState(pool, state); } } return state; } /** * A session wrapper that does not disposes if a transaction is active. * <p> * This wrapper provides for client code to follow de acquire/use/dispose workflow without * worrying if it should or should not actually dispose the session depending on a transaction * being in progress or not. * </p> * * @author Gabriel Roldan (TOPP) * @version $Id$ * @since 2.5.x * @source $URL: * http://svn.geotools.org/trunk/modules/plugin/arcsde/datastore/src/main/java/org/ * geotools/arcsde/pool/SessionTransactionState.java $ */ private static final class TransactionSession extends SessionWrapper { public TransactionSession(final ISession session) { super(session); } /** * Does not returns the session to the pool while a transaction is active. */ @Override public void dispose() throws IllegalStateException { if (isTransactionActive()) { LOGGER.finer("Ignoring Session.close, transaction is active..."); } else { wrapped.dispose(); } } } }