From 7d21f3e62a3f3e5674308531e3f17171b8505300 Mon Sep 17 00:00:00 2001 From: amckenzie Date: Thu, 19 Sep 2019 13:31:25 +0100 Subject: [PATCH] Add new commands AddTrigger and RemoveTrigger --- CHANGELOG.md | 4 + .../AddRemoveTriggerCommandHandler.java | 39 +++++ .../process/EventLogTriggerManipulator.java | 54 +++++++ .../EventStoreTriggerManipulatorProvider.java | 22 +++ .../ValidateCatchupCommandHandler.java | 22 +++ .../AddRemoveTriggerCommandHandlerTest.java | 65 ++++++++ .../EventLogTriggerManipulatorTest.java | 91 +++++++++++ ...ntStoreTriggerManipulatorProviderTest.java | 54 +++++++ .../ValidateCatchupCommandHandlerTest.java | 30 ++++ event-store-util/pom.xml | 6 + .../triggers/DatabaseTriggerManipulator.java | 142 ++++++++++++++++++ .../DatabaseTriggerManipulatorFactory.java | 10 ++ .../util/sql/triggers/TriggerData.java | 73 +++++++++ .../TriggerManipulationFailedException.java | 8 + ...DatabaseTriggerManipulatorFactoryTest.java | 32 ++++ .../DatabaseTriggerManipulatorIT.java | 122 +++++++++++++++ pom.xml | 4 +- 17 files changed, 776 insertions(+), 2 deletions(-) create mode 100644 event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandler.java create mode 100644 event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulator.java create mode 100644 event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProvider.java create mode 100644 event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandler.java create mode 100644 event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandlerTest.java create mode 100644 event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulatorTest.java create mode 100644 event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProviderTest.java create mode 100644 event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandlerTest.java create mode 100644 event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulator.java create mode 100644 event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactory.java create mode 100644 event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerData.java create mode 100644 event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerManipulationFailedException.java create mode 100644 event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactoryTest.java create mode 100644 event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorIT.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e32f49a..d6f263bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ on [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to ## [Unreleased] +## [2.0.19] - 2019-09-19 +### Added +- New SystemCommands AddTrigger and RemoveTrigger to manage the trigger on the event_log table + ## [2.0.18] - 2019-09-18 ### Changed - Use DefaultEnvelopeProvider in MetadataEventNumberUpdater directly to fix classloading errors during rebuild diff --git a/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandler.java b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandler.java new file mode 100644 index 000000000..6ae82611a --- /dev/null +++ b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandler.java @@ -0,0 +1,39 @@ +package uk.gov.justice.services.eventstore.management.untrigger.commands; + +import static java.lang.String.format; +import static uk.gov.justice.services.jmx.api.command.AddTriggerCommand.ADD_TRIGGER; +import static uk.gov.justice.services.jmx.api.command.RemoveTriggerCommand.REMOVE_TRIGGER; + +import uk.gov.justice.services.eventstore.management.untrigger.process.EventLogTriggerManipulator; +import uk.gov.justice.services.jmx.api.command.AddTriggerCommand; +import uk.gov.justice.services.jmx.api.command.RemoveTriggerCommand; +import uk.gov.justice.services.jmx.command.HandlesSystemCommand; + +import javax.inject.Inject; + +import org.slf4j.Logger; + +public class AddRemoveTriggerCommandHandler { + + @Inject + private EventLogTriggerManipulator eventLogTriggerManipulator; + + @Inject + private Logger logger; + + @HandlesSystemCommand(ADD_TRIGGER) + public void addTriggerToEventLogTable(final AddTriggerCommand addTriggerCommand) { + + logger.info(format("Received command %s", addTriggerCommand.getName())); + + eventLogTriggerManipulator.addTriggerToEventLogTable(); + } + + @HandlesSystemCommand(REMOVE_TRIGGER) + public void removeTriggerFromEventLogTable(final RemoveTriggerCommand removeTriggerCommand) { + + logger.info(format("Received command %s", removeTriggerCommand.getName())); + + eventLogTriggerManipulator.removeTriggerFromEventLogTable(); + } +} diff --git a/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulator.java b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulator.java new file mode 100644 index 000000000..b9634f1c9 --- /dev/null +++ b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulator.java @@ -0,0 +1,54 @@ +package uk.gov.justice.services.eventstore.management.untrigger.process; + +import static java.lang.String.format; + +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulator; + +import javax.inject.Inject; + +import org.slf4j.Logger; + +public class EventLogTriggerManipulator { + + private static final String TRIGGER_NAME = "queue_publish_event"; + private static final String TABLE_NAME = "event_log"; + private static final String ACTION = "EXECUTE PROCEDURE update_publish_queue()"; + + @Inject + private EventStoreTriggerManipulatorProvider eventStoreTriggerManipulatorProvider; + + @Inject + private Logger logger; + + public void addTriggerToEventLogTable() { + + final DatabaseTriggerManipulator databaseTriggerManipulator = eventStoreTriggerManipulatorProvider + .getDatabaseTriggerManipulator(); + + if (databaseTriggerManipulator.findTriggerOnTable(TRIGGER_NAME, TABLE_NAME).isPresent()) { + logger.warn(format("Trigger '%s' already exists on %s table", TRIGGER_NAME, TABLE_NAME)); + } else { + + databaseTriggerManipulator + .addInsertTriggerToTable(TRIGGER_NAME, TABLE_NAME, ACTION); + + logger.info(format("Trigger '%s' successfully added to %s table", TRIGGER_NAME, TABLE_NAME)); + } + } + + public void removeTriggerFromEventLogTable() { + + final DatabaseTriggerManipulator databaseTriggerManipulator = eventStoreTriggerManipulatorProvider + .getDatabaseTriggerManipulator(); + + if (databaseTriggerManipulator.findTriggerOnTable(TRIGGER_NAME, TABLE_NAME).isPresent()) { + + databaseTriggerManipulator + .removeTriggerFromTable(TRIGGER_NAME, TABLE_NAME); + + logger.info(format("Removed trigger '%s' from %s table", TRIGGER_NAME, TABLE_NAME)); + } else { + logger.warn(format("No trigger named '%s' found on %s table", TRIGGER_NAME, TABLE_NAME)); + } + } +} diff --git a/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProvider.java b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProvider.java new file mode 100644 index 000000000..0b2f573f4 --- /dev/null +++ b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProvider.java @@ -0,0 +1,22 @@ +package uk.gov.justice.services.eventstore.management.untrigger.process; + +import uk.gov.justice.services.eventsourcing.source.core.EventStoreDataSourceProvider; +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulator; +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulatorFactory; + +import javax.inject.Inject; +import javax.sql.DataSource; + +public class EventStoreTriggerManipulatorProvider { + + @Inject + private EventStoreDataSourceProvider eventStoreDataSourceProvider; + + @Inject + public DatabaseTriggerManipulatorFactory databaseTriggerManipulatorFactory; + + public DatabaseTriggerManipulator getDatabaseTriggerManipulator() { + final DataSource defaultDataSource = eventStoreDataSourceProvider.getDefaultDataSource(); + return databaseTriggerManipulatorFactory.databaseTriggerManipulator(defaultDataSource); + } +} diff --git a/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandler.java b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandler.java new file mode 100644 index 000000000..065dfed90 --- /dev/null +++ b/event-store-management/src/main/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandler.java @@ -0,0 +1,22 @@ +package uk.gov.justice.services.eventstore.management.validation.commands; + +import static java.lang.String.format; +import static uk.gov.justice.services.jmx.api.command.ValidateCatchupCommand.VALIDATE_CATCHUP; + +import uk.gov.justice.services.jmx.api.command.ValidateCatchupCommand; +import uk.gov.justice.services.jmx.command.HandlesSystemCommand; + +import javax.inject.Inject; + +import org.slf4j.Logger; + +public class ValidateCatchupCommandHandler { + + @Inject + private Logger logger; + + @HandlesSystemCommand(VALIDATE_CATCHUP) + public void validateCatchup(final ValidateCatchupCommand validateCatchupCommand) { + logger.warn(format("Command %s not yet implemented", validateCatchupCommand.getName())); + } +} diff --git a/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandlerTest.java b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandlerTest.java new file mode 100644 index 000000000..c09195b61 --- /dev/null +++ b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/commands/AddRemoveTriggerCommandHandlerTest.java @@ -0,0 +1,65 @@ +package uk.gov.justice.services.eventstore.management.untrigger.commands; + +import static java.lang.String.format; +import static org.junit.Assert.*; + +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.*; + +import uk.gov.justice.services.eventstore.management.untrigger.process.EventLogTriggerManipulator; +import uk.gov.justice.services.jmx.api.command.AddTriggerCommand; +import uk.gov.justice.services.jmx.api.command.RemoveTriggerCommand; + +import javax.inject.Inject; + +@RunWith(MockitoJUnitRunner.class) +public class AddRemoveTriggerCommandHandlerTest { + + @Mock + private EventLogTriggerManipulator eventLogTriggerManipulator; + + @Mock + private Logger logger; + + @InjectMocks + private AddRemoveTriggerCommandHandler addRemoveTriggerCommandHandler; + + @Test + public void shouldCallTheAddEventLogTriggerProcess() throws Exception { + + final AddTriggerCommand addTriggerCommand = new AddTriggerCommand(); + + addRemoveTriggerCommandHandler.addTriggerToEventLogTable(addTriggerCommand); + + verify(logger).info("Received command ADD_TRIGGER"); + verify(eventLogTriggerManipulator).addTriggerToEventLogTable(); + } + + @Test + public void shouldCallTheRemoveEventLogTriggerProcess() throws Exception { + + final RemoveTriggerCommand removeTriggerCommand = new RemoveTriggerCommand(); + + addRemoveTriggerCommandHandler.removeTriggerFromEventLogTable(removeTriggerCommand); + + verify(logger).info("Received command REMOVE_TRIGGER"); + verify(eventLogTriggerManipulator).removeTriggerFromEventLogTable(); + } +} diff --git a/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulatorTest.java b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulatorTest.java new file mode 100644 index 000000000..cf6764372 --- /dev/null +++ b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventLogTriggerManipulatorTest.java @@ -0,0 +1,91 @@ +package uk.gov.justice.services.eventstore.management.untrigger.process; + +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulator; +import uk.gov.justice.services.eventsourcing.util.sql.triggers.TriggerData; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; + +@RunWith(MockitoJUnitRunner.class) +public class EventLogTriggerManipulatorTest { + + @Mock + private EventStoreTriggerManipulatorProvider eventStoreTriggerManipulatorProvider; + + @Mock + private Logger logger; + + @InjectMocks + private EventLogTriggerManipulator eventLogTriggerManipulator; + + @Test + public void shouldAddTriggerToEventLogTable() throws Exception { + + final DatabaseTriggerManipulator databaseTriggerManipulator = mock(DatabaseTriggerManipulator.class); + + when(eventStoreTriggerManipulatorProvider.getDatabaseTriggerManipulator()).thenReturn(databaseTriggerManipulator); + when(databaseTriggerManipulator.findTriggerOnTable("queue_publish_event", "event_log")).thenReturn(empty()); + + eventLogTriggerManipulator.addTriggerToEventLogTable(); + + verify(databaseTriggerManipulator).addInsertTriggerToTable("queue_publish_event", "event_log", "EXECUTE PROCEDURE update_publish_queue()"); + verify(logger).info("Trigger 'queue_publish_event' successfully added to event_log table"); + } + + @Test + public void shouldNotAddTriggerIfItAlreadyExists() throws Exception { + + final TriggerData triggerData = mock(TriggerData.class); + final DatabaseTriggerManipulator databaseTriggerManipulator = mock(DatabaseTriggerManipulator.class); + + when(eventStoreTriggerManipulatorProvider.getDatabaseTriggerManipulator()).thenReturn(databaseTriggerManipulator); + when(databaseTriggerManipulator.findTriggerOnTable("queue_publish_event", "event_log")).thenReturn(of(triggerData)); + + eventLogTriggerManipulator.addTriggerToEventLogTable(); + + verify(logger).warn("Trigger 'queue_publish_event' already exists on event_log table"); + verify(databaseTriggerManipulator, never()).addInsertTriggerToTable(anyString(), anyString(), anyString()); + } + + @Test + public void shouldRemoveTriggerFromEventLogTable() throws Exception { + + final DatabaseTriggerManipulator databaseTriggerManipulator = mock(DatabaseTriggerManipulator.class); + final TriggerData triggerData = mock(TriggerData.class); + + when(eventStoreTriggerManipulatorProvider.getDatabaseTriggerManipulator()).thenReturn(databaseTriggerManipulator); + when(databaseTriggerManipulator.findTriggerOnTable("queue_publish_event", "event_log")).thenReturn(of(triggerData)); + + eventLogTriggerManipulator.removeTriggerFromEventLogTable(); + + verify(databaseTriggerManipulator).removeTriggerFromTable("queue_publish_event", "event_log"); + verify(logger).info("Removed trigger 'queue_publish_event' from event_log table"); + } + + @Test + public void shouldNotRemoveTriggerIfItNoTriggerExists() throws Exception { + + final DatabaseTriggerManipulator databaseTriggerManipulator = mock(DatabaseTriggerManipulator.class); + + when(eventStoreTriggerManipulatorProvider.getDatabaseTriggerManipulator()).thenReturn(databaseTriggerManipulator); + when(databaseTriggerManipulator.findTriggerOnTable("queue_publish_event", "event_log")).thenReturn(empty()); + + eventLogTriggerManipulator.removeTriggerFromEventLogTable(); + + verify(logger).warn("No trigger named 'queue_publish_event' found on event_log table"); + verify(databaseTriggerManipulator, never()).removeTriggerFromTable(anyString(), anyString()); + } +} diff --git a/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProviderTest.java b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProviderTest.java new file mode 100644 index 000000000..df0e6f4a6 --- /dev/null +++ b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/untrigger/process/EventStoreTriggerManipulatorProviderTest.java @@ -0,0 +1,54 @@ +package uk.gov.justice.services.eventstore.management.untrigger.process; + +import static org.junit.Assert.*; + +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.*; + +import uk.gov.justice.services.eventsourcing.source.core.EventStoreDataSourceProvider; +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulator; +import uk.gov.justice.services.eventsourcing.util.sql.triggers.DatabaseTriggerManipulatorFactory; + +import javax.inject.Inject; +import javax.sql.DataSource; + +@RunWith(MockitoJUnitRunner.class) +public class EventStoreTriggerManipulatorProviderTest { + + @Mock + private EventStoreDataSourceProvider eventStoreDataSourceProvider; + + @Mock + public DatabaseTriggerManipulatorFactory databaseTriggerManipulatorFactory; + + @InjectMocks + private EventStoreTriggerManipulatorProvider eventStoreTriggerManipulatorProvider; + + @Test + public void shouldCreateATriggerManipulaterWithTheCorrectEventStoreDataSource() throws Exception { + + final DataSource eventStoreDataSource = mock(DataSource.class); + final DatabaseTriggerManipulator databaseTriggerManipulator = mock(DatabaseTriggerManipulator.class); + + when(eventStoreDataSourceProvider.getDefaultDataSource()).thenReturn(eventStoreDataSource); + when(databaseTriggerManipulatorFactory.databaseTriggerManipulator(eventStoreDataSource)).thenReturn(databaseTriggerManipulator); + + assertThat(eventStoreTriggerManipulatorProvider.getDatabaseTriggerManipulator(), is(databaseTriggerManipulator)); + } +} diff --git a/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandlerTest.java b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandlerTest.java new file mode 100644 index 000000000..23d2a1abe --- /dev/null +++ b/event-store-management/src/test/java/uk/gov/justice/services/eventstore/management/validation/commands/ValidateCatchupCommandHandlerTest.java @@ -0,0 +1,30 @@ +package uk.gov.justice.services.eventstore.management.validation.commands; + +import static org.mockito.Mockito.verify; + +import uk.gov.justice.services.jmx.api.command.ValidateCatchupCommand; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.slf4j.Logger; + +@RunWith(MockitoJUnitRunner.class) +public class ValidateCatchupCommandHandlerTest { + + @Mock + private Logger logger; + + @InjectMocks + private ValidateCatchupCommandHandler validateCatchupCommandHandler; + + @Test + public void shouldLogNotImplemented() throws Exception { + + validateCatchupCommandHandler.validateCatchup(new ValidateCatchupCommand()); + + verify(logger).warn("Command VALIDATE_CATCHUP not yet implemented"); + } +} diff --git a/event-store-util/pom.xml b/event-store-util/pom.xml index e4c47dbdc..f935adac8 100644 --- a/event-store-util/pom.xml +++ b/event-store-util/pom.xml @@ -38,6 +38,12 @@ mockito-core test + + uk.gov.justice.event-store + test-utils-persistence + ${project.version} + test + diff --git a/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulator.java b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulator.java new file mode 100644 index 000000000..9c5777f34 --- /dev/null +++ b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulator.java @@ -0,0 +1,142 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +import static java.lang.String.format; +import static java.util.Optional.empty; +import static java.util.Optional.of; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.sql.DataSource; + +public class DatabaseTriggerManipulator { + + private final DataSource dataSource; + + public DatabaseTriggerManipulator(final DataSource dataSource) { + this.dataSource = dataSource; + } + + public void removeTriggerFromTable(final String triggerName, final String tableName) { + + executeStatement(format("DROP TRIGGER %s on %s CASCADE", triggerName, tableName)); + } + + + public void addInsertTriggerToTable(final String triggerName, final String tableName, final String action) { + + final String statement = format("CREATE TRIGGER %s AFTER INSERT ON %s FOR EACH ROW %s", triggerName, tableName, action); + + executeStatement(statement); + } + + public List listTriggersOnTable(final String tableName) { + + final String statement = + "SELECT " + + "trigger_name, " + + "event_manipulation, " + + "action_statement, " + + "action_timing " + + "FROM information_schema.triggers " + + "WHERE event_object_table = ? " + + "ORDER BY event_object_table"; + + final List triggerDataList = new ArrayList<>(); + + try (final Connection connection = dataSource.getConnection(); + final PreparedStatement preparedStatement = connection.prepareStatement(statement)) { + + preparedStatement.setString(1, tableName); + + try (final ResultSet resultSet = preparedStatement.executeQuery()) { + + while (resultSet.next()) { + final String triggerName = resultSet.getString("trigger_name"); + final String manipulationType = resultSet.getString("event_manipulation"); + final String action = resultSet.getString("action_statement"); + final String timing = resultSet.getString("action_timing"); + + final TriggerData triggerData = new TriggerData( + tableName, + triggerName, + manipulationType, + action, + timing + ); + + triggerDataList.add(triggerData); + } + + } + + } catch (final SQLException e) { + throw new TriggerManipulationFailedException(format("Failed to list triggers on %s table", tableName), e); + } + + return triggerDataList; + } + + public Optional findTriggerOnTable(final String triggerName, final String tableName) { + + final String statement = + "SELECT " + + "event_object_table, " + + "event_manipulation, " + + "action_statement, " + + "action_timing " + + "FROM information_schema.triggers " + + "WHERE event_object_table = ? " + + "AND trigger_name = ? " + + "ORDER BY event_object_table"; + + try (final Connection connection = dataSource.getConnection(); + final PreparedStatement preparedStatement = connection.prepareStatement(statement)) { + + preparedStatement.setString(1, tableName); + preparedStatement.setString(2, triggerName); + + try (final ResultSet resultSet = preparedStatement.executeQuery()) { + + if (resultSet.next()) { + final String table = resultSet.getString("event_object_table"); + final String manipulationType = resultSet.getString("event_manipulation"); + final String action = resultSet.getString("action_statement"); + final String timing = resultSet.getString("action_timing"); + + final TriggerData triggerData = new TriggerData( + table, + triggerName, + manipulationType, + action, + timing + ); + + return of(triggerData); + } + } + + } catch (final SQLException e) { + throw new TriggerManipulationFailedException(format("Failed to find trigger on %s table", tableName), e); + } + + return empty(); + } + + private void executeStatement(final String statement) { + try (final Connection connection = dataSource.getConnection(); + final PreparedStatement preparedStatement = connection.prepareStatement(statement)) { + + preparedStatement.execute(); + + } catch (final SQLException e) { + throw new TriggerManipulationFailedException(format("Failed to run statement '%s'", statement), e); + } + + } +} diff --git a/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactory.java b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactory.java new file mode 100644 index 000000000..01ce23606 --- /dev/null +++ b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactory.java @@ -0,0 +1,10 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +import javax.sql.DataSource; + +public class DatabaseTriggerManipulatorFactory { + + public DatabaseTriggerManipulator databaseTriggerManipulator(final DataSource dataSource) { + return new DatabaseTriggerManipulator(dataSource); + } +} diff --git a/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerData.java b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerData.java new file mode 100644 index 000000000..a0cf52120 --- /dev/null +++ b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerData.java @@ -0,0 +1,73 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +import java.util.Objects; + +public class TriggerData { + + private final String tableName; + private final String triggerName; + private final String manipulationType; + private final String action; + private final String timing; + + public TriggerData( + final String tableName, + final String triggerName, + final String manipulationType, + final String action, + final String timing) { + this.tableName = tableName; + this.triggerName = triggerName; + this.manipulationType = manipulationType; + this.action = action; + this.timing = timing; + } + + public String getTableName() { + return tableName; + } + + public String getTriggerName() { + return triggerName; + } + + public String getManipulationType() { + return manipulationType; + } + + public String getAction() { + return action; + } + + public String getTiming() { + return timing; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof TriggerData)) return false; + final TriggerData that = (TriggerData) o; + return Objects.equals(tableName, that.tableName) && + Objects.equals(triggerName, that.triggerName) && + Objects.equals(manipulationType, that.manipulationType) && + Objects.equals(action, that.action) && + Objects.equals(timing, that.timing); + } + + @Override + public int hashCode() { + return Objects.hash(tableName, triggerName, manipulationType, action, timing); + } + + @Override + public String toString() { + return "TriggerData{" + + "tableName='" + tableName + '\'' + + ", triggerName='" + triggerName + '\'' + + ", manipulationType='" + manipulationType + '\'' + + ", action='" + action + '\'' + + ", timing='" + timing + '\'' + + '}'; + } +} diff --git a/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerManipulationFailedException.java b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerManipulationFailedException.java new file mode 100644 index 000000000..5f2672174 --- /dev/null +++ b/event-store-util/src/main/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/TriggerManipulationFailedException.java @@ -0,0 +1,8 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +public class TriggerManipulationFailedException extends RuntimeException { + + public TriggerManipulationFailedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactoryTest.java b/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactoryTest.java new file mode 100644 index 000000000..cd6e484da --- /dev/null +++ b/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorFactoryTest.java @@ -0,0 +1,32 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +import uk.gov.justice.services.test.utils.core.reflection.ReflectionUtil; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class DatabaseTriggerManipulatorFactoryTest { + + @InjectMocks + private DatabaseTriggerManipulatorFactory databaseTriggerManipulatorFactory; + + @Test + public void shouldCreateDatabaseTriggerManipulator() throws Exception { + + final DataSource dataSource = mock(DataSource.class); + + final DatabaseTriggerManipulator databaseTriggerManipulator = databaseTriggerManipulatorFactory.databaseTriggerManipulator(dataSource); + + assertThat(ReflectionUtil.getValueOfField(databaseTriggerManipulator, "dataSource", DataSource.class), is(dataSource)); + + } +} diff --git a/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorIT.java b/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorIT.java new file mode 100644 index 000000000..8e24ca1c6 --- /dev/null +++ b/event-store-util/src/test/java/uk/gov/justice/services/eventsourcing/util/sql/triggers/DatabaseTriggerManipulatorIT.java @@ -0,0 +1,122 @@ +package uk.gov.justice.services.eventsourcing.util.sql.triggers; + +import static java.util.Optional.empty; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.*; + +import uk.gov.justice.services.test.utils.persistence.TestJdbcDataSourceProvider; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.Optional; + +import javax.sql.DataSource; + + +public class DatabaseTriggerManipulatorIT { + + private static final String CONTEXT_NAME = "framework"; + private static final String TEMPORARY_TABLE_NAME = "for_testing_delete_me"; + + private final TestJdbcDataSourceProvider testJdbcDataSourceProvider = new TestJdbcDataSourceProvider(); + private final DataSource eventStoreDataSource = testJdbcDataSourceProvider.getEventStoreDataSource(CONTEXT_NAME); + + private DatabaseTriggerManipulator databaseTriggerManipulator = new DatabaseTriggerManipulator(eventStoreDataSource); + + @Before + public void createTemporaryTable()throws Exception { + + final String createTabelSql = "CREATE TABLE " + TEMPORARY_TABLE_NAME + " (name varchar(255))"; + + try(final Connection connection = eventStoreDataSource.getConnection(); + final PreparedStatement preparedStatement = connection.prepareStatement(createTabelSql)) { + preparedStatement.execute(); + } + } + + @After + public void dropTemporaryTable()throws Exception { + + final String dropTabelSql = "DROP TABLE " + TEMPORARY_TABLE_NAME; + + try(final Connection connection = eventStoreDataSource.getConnection(); + final PreparedStatement preparedStatement = connection.prepareStatement(dropTabelSql)) { + preparedStatement.execute(); + } + } + + @Test + public void shouldAddRemoveAndListTriggersOnTable() throws Exception { + + final String triggerName = "temp_trigger"; + final String action = "EXECUTE PROCEDURE update_publish_queue()"; + + assertThat(databaseTriggerManipulator.listTriggersOnTable(TEMPORARY_TABLE_NAME).isEmpty(), is(true)); + + databaseTriggerManipulator.addInsertTriggerToTable(triggerName, TEMPORARY_TABLE_NAME, action); + + final List triggerData = databaseTriggerManipulator.listTriggersOnTable(TEMPORARY_TABLE_NAME); + + assertThat(triggerData.size(), is(1)); + assertThat(triggerData.get(0).getTriggerName(), is(triggerName)); + assertThat(triggerData.get(0).getTableName(), is(TEMPORARY_TABLE_NAME)); + assertThat(triggerData.get(0).getManipulationType(), is("INSERT")); + assertThat(triggerData.get(0).getAction(), is(action)); + assertThat(triggerData.get(0).getTiming(), is("AFTER")); + + databaseTriggerManipulator.removeTriggerFromTable(triggerName, TEMPORARY_TABLE_NAME); + + assertThat(databaseTriggerManipulator.listTriggersOnTable(TEMPORARY_TABLE_NAME).isEmpty(), is(true)); + } + + @Test + public void shouldFindTriggerByItsName() throws Exception { + + final String triggerName = "temp_trigger"; + final String action = "EXECUTE PROCEDURE update_publish_queue()"; + + assertThat(databaseTriggerManipulator.listTriggersOnTable(TEMPORARY_TABLE_NAME).isEmpty(), is(true)); + + databaseTriggerManipulator.addInsertTriggerToTable(triggerName, TEMPORARY_TABLE_NAME, action); + + final Optional triggerOnTable = databaseTriggerManipulator.findTriggerOnTable(triggerName, TEMPORARY_TABLE_NAME); + + if(triggerOnTable.isPresent()) { + + final TriggerData triggerData = triggerOnTable.get(); + + assertThat(triggerData.getTriggerName(), is(triggerName)); + assertThat(triggerData.getTableName(), is(TEMPORARY_TABLE_NAME)); + assertThat(triggerData.getManipulationType(), is("INSERT")); + assertThat(triggerData.getAction(), is(action)); + assertThat(triggerData.getTiming(), is("AFTER")); + } + } + + @Test + public void shouldReturnEmptyIfNoTriggerExists() throws Exception { + + assertThat(databaseTriggerManipulator.findTriggerOnTable("some_other_trigger", TEMPORARY_TABLE_NAME), is(empty())); + } +} diff --git a/pom.xml b/pom.xml index 6c3b0a207..7715856d6 100644 --- a/pom.xml +++ b/pom.xml @@ -39,8 +39,8 @@ event-store - 4.0.1 - 6.0.14 + 4.1.0 + 6.0.15 2.4.0 1.20.1 1.24.3