Guest User

Untitled

a guest
Jan 11th, 2018
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.90 KB | None | 0 0
  1. package example.postgres.audit;
  2.  
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5.  
  6. import java.sql.CallableStatement;
  7. import java.sql.Connection;
  8. import java.sql.PreparedStatement;
  9. import java.sql.SQLException;
  10. import java.sql.Savepoint;
  11. import java.sql.Statement;
  12. import java.sql.Timestamp;
  13. import java.time.Instant;
  14. import java.util.Objects;
  15. import java.util.UUID;
  16.  
  17. import static java.lang.String.format;
  18.  
  19. /**
  20. * Whenever the DB is manipulated through this connection, Postgres receives the context variables used in the 'trigger_audit' triggers beforehand.
  21. */
  22. public class AuditingConnection extends DelegatingConnection {
  23.  
  24. private static final Logger LOGGER = LoggerFactory.getLogger(AuditingConnection.class);
  25.  
  26. // TODO: implement the static method `AuditContextProvider.currentUserId()`,
  27. // returning whatever value you'd like persisted in the columns `inserted_by`/`updated_by`
  28. // from a thread-local context
  29. private static final AuditContextProvider AUDIT_CONTEXT_PROVIDER = new AuditContextProvider();
  30.  
  31. /**
  32. * Remembers the audit.AUDIT_USER set for the ongoing transaction, such that we only send SET LOCAL commands when necessary.
  33. */
  34. private UUID userCommunicatedToDB;
  35.  
  36. public AuditingConnection(Connection wrappedConnection) {
  37. super(wrappedConnection);
  38. }
  39.  
  40. private void setContext() {
  41. UUID user = AUDIT_CONTEXT_PROVIDER.currentUserId();
  42.  
  43. // The user reported by AUDIT_CONTEXT_PROVIDER may change within the same ongoing transaction,
  44. // e.g. because the user session is initialized after the first DB statements.
  45. // Detect changes and propagate the information to the DB iff necessary:
  46. if (!Objects.equals(user, userCommunicatedToDB)) {
  47. Timestamp nowTimestamp = Timestamp.from(Instant.now());
  48.  
  49. if (isAutoCommit()) {
  50. // If SET LOCAL is called outside a transaction, it has no effect besides Postgres issuing a warning.
  51. // BUT WHY THE HELL ARE WE USING AUTO-COMMIT???
  52. LOGGER.warn("Did not set AUDIT context variables because connection was in AUTO-COMMIT mode.");
  53. return;
  54. }
  55.  
  56. try (Statement auditUserStmt = createStatementWithoutAuditing();
  57. Statement auditTimestampStmt = createStatementWithoutAuditing()) {
  58. auditUserStmt.execute(format("SET LOCAL audit.AUDIT_USER = '%s'", user.toString()));
  59. auditTimestampStmt.execute(format("SET LOCAL audit.AUDIT_TIMESTAMP = '%s'", nowTimestamp.toString()));
  60. } catch (SQLException e) {
  61. LOGGER.warn("failed to set AUDIT information", e);
  62. }
  63.  
  64. this.userCommunicatedToDB = user;
  65. }
  66. }
  67.  
  68. private Statement createStatementWithoutAuditing() throws SQLException {
  69. return super.createStatement();
  70. }
  71.  
  72. private void onTransactionEnd() {
  73. // Values set with SET LOCAL are purged automatically by Postgres after a COMMIT/ROLLBACK [TO SAVEPOINT].
  74. this.userCommunicatedToDB = null;
  75. }
  76.  
  77. private boolean isAutoCommit() {
  78. try {
  79. return getAutoCommit();
  80. } catch (SQLException e) {
  81. LOGGER.warn("failed to read AUTO-COMMIT status", e);
  82. return false; // so we attempt to set the audit info anyway...
  83. }
  84. }
  85.  
  86. /*
  87.  
  88. DELEGATION WITH AUDITING:
  89. These methods either manipulate data in the DB or the transaction state of this Connection.
  90. All other methods don't and therefore use direct delegation.
  91.  
  92. */
  93.  
  94. @Override
  95. public Statement createStatement() throws SQLException {
  96. setContext();
  97. return super.createStatement();
  98. }
  99.  
  100. @Override
  101. public PreparedStatement prepareStatement(String sql) throws SQLException {
  102. setContext();
  103. return super.prepareStatement(sql);
  104. }
  105.  
  106. @Override
  107. public CallableStatement prepareCall(String sql) throws SQLException {
  108. setContext();
  109. return super.prepareCall(sql);
  110. }
  111.  
  112. @Override
  113. public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
  114. setContext();
  115. return super.createStatement(resultSetType, resultSetConcurrency);
  116. }
  117.  
  118. @Override
  119. public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
  120. setContext();
  121. return super.prepareStatement(sql, resultSetType, resultSetConcurrency);
  122. }
  123.  
  124. @Override
  125. public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
  126. setContext();
  127. return super.prepareCall(sql, resultSetType, resultSetConcurrency);
  128. }
  129.  
  130. @Override
  131. public void setAutoCommit(boolean autoCommit) throws SQLException {
  132. super.setAutoCommit(autoCommit);
  133. onTransactionEnd();
  134. }
  135.  
  136. @Override
  137. public void commit() throws SQLException {
  138. super.commit();
  139. onTransactionEnd();
  140. }
  141.  
  142. @Override
  143. public void rollback() throws SQLException {
  144. super.rollback();
  145. onTransactionEnd();
  146. }
  147.  
  148. @Override
  149. public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  150. setContext();
  151. return super.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
  152. }
  153.  
  154. @Override
  155. public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  156. setContext();
  157. return super.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
  158. }
  159.  
  160. @Override
  161. public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  162. setContext();
  163. return super.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
  164. }
  165.  
  166. @Override
  167. public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
  168. setContext();
  169. return super.prepareStatement(sql, autoGeneratedKeys);
  170. }
  171.  
  172. @Override
  173. public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
  174. setContext();
  175. return super.prepareStatement(sql, columnIndexes);
  176. }
  177.  
  178. @Override
  179. public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
  180. setContext();
  181. return super.prepareStatement(sql, columnNames);
  182. }
  183.  
  184. @Override
  185. public void rollback(Savepoint savepoint) throws SQLException {
  186. super.rollback(savepoint);
  187. // if the SET LOCAL happened before the 'setSavepoint(savepoint)' call, we might have lost our context values:
  188. onTransactionEnd();
  189. }
  190.  
  191. @Override
  192. public void setTransactionIsolation(int level) throws SQLException {
  193. super.setTransactionIsolation(level);
  194. onTransactionEnd();
  195. }
  196.  
  197. }
Add Comment
Please, Sign In to add comment