View Javadoc

1   package com.atlassian.sal.core.rdbms;
2   
3   import com.atlassian.sal.api.rdbms.ConnectionCallback;
4   import com.atlassian.sal.api.rdbms.TransactionalExecutor;
5   import com.atlassian.sal.spi.HostConnectionAccessor;
6   import com.google.common.annotations.VisibleForTesting;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  import java.sql.Connection;
11  import java.sql.SQLException;
12  import javax.annotation.Nonnull;
13  
14  /**
15   * Default implementation that invokes {@link com.atlassian.sal.spi.HostConnectionAccessor}.
16   * <p/>
17   * Created by {@link com.atlassian.sal.core.rdbms.DefaultTransactionalExecutorFactory}
18   *
19   * @since 3.0
20   */
21  public class DefaultTransactionalExecutor implements TransactionalExecutor
22  {
23      private static final Logger log = LoggerFactory.getLogger(DefaultTransactionalExecutor.class);
24  
25      private final HostConnectionAccessor hostConnectionAccessor;
26  
27      @VisibleForTesting
28      boolean readOnly;
29  
30      @VisibleForTesting
31      boolean newTransaction;
32  
33      public DefaultTransactionalExecutor(@Nonnull final HostConnectionAccessor hostConnectionAccessor, final boolean readOnly, final boolean newTransaction)
34      {
35          this.hostConnectionAccessor = hostConnectionAccessor;
36          this.readOnly = readOnly;
37          this.newTransaction = newTransaction;
38      }
39  
40      @Override
41      public <A> A execute(@Nonnull final ConnectionCallback<A> callback)
42      {
43          return hostConnectionAccessor.execute(readOnly, newTransaction, new ConnectionCallback<A>()
44          {
45              @Override
46              public A execute(final Connection connection)
47              {
48                  return executeInternal(connection, callback);
49              }
50          });
51      }
52  
53      @Override
54      @Nonnull
55      public TransactionalExecutor readOnly()
56      {
57          readOnly = true;
58          return this;
59      }
60  
61      @Override
62      @Nonnull
63      public TransactionalExecutor readWrite()
64      {
65          readOnly = false;
66          return this;
67      }
68  
69      @Override
70      @Nonnull
71      public TransactionalExecutor newTransaction()
72      {
73          newTransaction = true;
74          return this;
75      }
76  
77      @Override
78      @Nonnull
79      public TransactionalExecutor existingTransaction()
80      {
81          newTransaction = false;
82          return this;
83      }
84  
85      @VisibleForTesting
86      <A> A executeInternal(@Nonnull final Connection connection, @Nonnull final ConnectionCallback<A> callback)
87      {
88          assertAutoCommitFalse(connection);
89  
90          // give the user the restricted connection
91          final WrappedConnection wrappedConnection = new WrappedConnection(connection);
92          try
93          {
94              // execute the user's callback
95              final A result = callback.execute(wrappedConnection);
96              try
97              {
98                  // no exception indicates success
99                  connection.commit();
100             }
101             catch (final SQLException se)
102             {
103                 // failure to commit should be propagated, to halt any further processing
104                 throw new RuntimeException("Unable to commit connection", se);
105             }
106             return result;
107         }
108         catch (final RuntimeException re)
109         {
110             try
111             {
112                 // any exception indicates failure
113                 connection.rollback();
114             }
115             catch (SQLException se)
116             {
117                 // swallow this because we want to rethrow the actual exception
118                 log.error("Unable to rollback connection: " + se.getMessage());
119             }
120             throw re;
121         }
122         finally
123         {
124             // more protection against the user retaining the connection for later use
125             wrappedConnection.expire();
126         }
127     }
128 
129     private void assertAutoCommitFalse(final Connection connection)
130     {
131         try
132         {
133             if (connection.getAutoCommit())
134             {
135                 throw new IllegalStateException("com.atlassian.sal.spi.HostConnectionAccessor returned connection with autocommit set");
136             }
137         }
138         catch (SQLException e)
139         {
140             throw new RuntimeException("unable to invoke java.sql.Connection#getAutoCommit", e);
141         }
142     }
143 }