From 47003077cc96a0798be5c309e1ac4e9f03a34082 Mon Sep 17 00:00:00 2001 From: Bojan Vukasovic Date: Sat, 24 Jun 2017 15:37:51 +0200 Subject: [PATCH] INT-4306: Add support for JdbcMetadataStore JIRA: https://jira.spring.io/browse/INT-4306 Fixed checkstyle errors Fixed checkstyle errors Fixed checkstyle errors and sql schema Manually fixed derby sql schema Fixing some issues added region support small refactorings test checkstyle fix integration tests and fixes integration tests and fixes --- build.gradle | 3 + composite_filter_bugfix | 0 .../src/test/java/int-derby.properties | 4 + ...leListFilterExternalStoreTests-context.xml | 26 ++ ...tOnceFileListFilterExternalStoreTests.java | 22 +- .../jdbc/metadata/JdbcMetadataStore.java | 223 ++++++++++++++++++ .../jdbc/metadata/package-info.java | 4 + .../integration/jdbc/schema-db2.sql | 8 + .../integration/jdbc/schema-derby.sql | 8 + .../integration/jdbc/schema-drop-db2.sql | 2 +- .../integration/jdbc/schema-drop-derby.sql | 2 +- .../integration/jdbc/schema-drop-h2.sql | 2 +- .../integration/jdbc/schema-drop-hsqldb.sql | 2 +- .../integration/jdbc/schema-drop-mysql.sql | 2 +- .../integration/jdbc/schema-drop-oracle.sql | 2 +- .../jdbc/schema-drop-postgresql.sql | 2 +- .../jdbc/schema-drop-sqlserver.sql | 2 +- .../integration/jdbc/schema-drop-sybase.sql | 2 +- .../integration/jdbc/schema-h2.sql | 8 + .../integration/jdbc/schema-hsqldb.sql | 8 + .../integration/jdbc/schema-mysql.sql | 8 + .../integration/jdbc/schema-oracle.sql | 8 + .../integration/jdbc/schema-postgresql.sql | 8 + .../integration/jdbc/schema-sqlserver.sql | 8 + .../integration/jdbc/schema-sybase.sql | 8 + .../src/main/sql/destroy.sql.vpp | 2 +- .../src/main/sql/schema.sql.vpp | 8 + .../JdbcMetadataStoreTests-context.xml | 26 ++ .../jdbc/metadata/JdbcMetadataStoreTests.java | 108 +++++++++ 29 files changed, 505 insertions(+), 11 deletions(-) create mode 100644 composite_filter_bugfix create mode 100644 spring-integration-file/src/test/java/int-derby.properties create mode 100644 spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests-context.xml create mode 100644 spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStore.java create mode 100644 spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/package-info.java create mode 100644 spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests-context.xml create mode 100644 spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests.java diff --git a/build.gradle b/build.gradle index 3b74c54a2c..f5940c99ad 100644 --- a/build.gradle +++ b/build.gradle @@ -337,6 +337,9 @@ project('spring-integration-file') { testCompile project(":spring-integration-redis") testCompile project(":spring-integration-redis").sourceSets.test.output testCompile project(":spring-integration-gemfire") + testCompile project(":spring-integration-jdbc") + testCompile "org.apache.derby:derby:$derbyVersion" + testCompile "org.apache.derby:derbyclient:$derbyVersion" testCompile "redis.clients:jedis:$jedisVersion" } } diff --git a/composite_filter_bugfix b/composite_filter_bugfix new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-integration-file/src/test/java/int-derby.properties b/spring-integration-file/src/test/java/int-derby.properties new file mode 100644 index 0000000000..0ecac5970d --- /dev/null +++ b/spring-integration-file/src/test/java/int-derby.properties @@ -0,0 +1,4 @@ +# Placeholders for Derby: +int.drop.script=classpath:/org/springframework/integration/jdbc/schema-drop-derby.sql +int.schema.script=classpath:/org/springframework/integration/jdbc/schema-derby.sql +int.database.incrementer.class=org.springframework.jdbc.support.incrementer.DerbyMaxValueIncrementer diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests-context.xml new file mode 100644 index 0000000000..83887861fc --- /dev/null +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java index 3cf82cae61..589a04b9af 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java @@ -26,17 +26,24 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import javax.sql.DataSource; + import org.apache.geode.cache.CacheFactory; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.integration.gemfire.metadata.GemfireMetadataStore; +import org.springframework.integration.jdbc.metadata.JdbcMetadataStore; import org.springframework.integration.metadata.ConcurrentMetadataStore; import org.springframework.integration.redis.metadata.RedisMetadataStore; import org.springframework.integration.redis.rules.RedisAvailable; import org.springframework.integration.redis.rules.RedisAvailableTests; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Gary Russell @@ -44,8 +51,14 @@ import org.springframework.integration.redis.rules.RedisAvailableTests; * @since 4.0 * */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext // close at the end after class public class PersistentAcceptOnceFileListFilterExternalStoreTests extends RedisAvailableTests { + @Autowired + private DataSource dataSource; + @Test @RedisAvailable public void testFileSystemWithRedisMetadataStore() throws Exception { @@ -69,6 +82,13 @@ public class PersistentAcceptOnceFileListFilterExternalStoreTests extends RedisA this.testFileSystem(new GemfireMetadataStore(new CacheFactory().create())); } + @Test + public void testFileSystemWithJdbcMetadataStore() throws Exception { + JdbcMetadataStore metadataStore = new JdbcMetadataStore(dataSource); + metadataStore.afterPropertiesSet(); + this.testFileSystem(metadataStore); + } + private void testFileSystem(ConcurrentMetadataStore store) throws Exception { final AtomicBoolean suspend = new AtomicBoolean(); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStore.java new file mode 100644 index 0000000000..ab5dfd35b3 --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStore.java @@ -0,0 +1,223 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed 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.springframework.integration.jdbc.metadata; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.integration.metadata.ConcurrentMetadataStore; +import org.springframework.integration.metadata.MetadataStore; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +/** + * Implementation of {@link MetadataStore} using a relational database via JDBC. SQL scripts to create the necessary + * tables are packaged as org/springframework/integration/jdbc/schema-*.sql, where * is the + * target database type. + * + * @author Bojan Vukasovic + * @since 5.0 + */ +public class JdbcMetadataStore implements ConcurrentMetadataStore, InitializingBean { + + /** + * Default value for the table prefix property. + */ + public static final String DEFAULT_TABLE_PREFIX = "INT_"; + + private volatile String tablePrefix = DEFAULT_TABLE_PREFIX; + + private volatile String region = "DEFAULT"; + + private String getValueQuery = "SELECT METADATA_VALUE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?"; + + private String getValueForUpdateQuery = "SELECT METADATA_VALUE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? FOR UPDATE"; + + private String replaceValueQuery = "UPDATE %SMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND METADATA_VALUE=? AND REGION=?"; + + private String replaceValueByKeyQuery = "UPDATE %SMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND REGION=?"; + + private String removeValueQuery = "DELETE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?"; + + private String putIfAbsentValueQuery = "INSERT INTO %SMETADATA_STORE(METADATA_KEY, METADATA_VALUE, REGION) " + + "SELECT ?, ?, ? FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? HAVING COUNT(*)=0"; + + private final JdbcOperations jdbcTemplate; + + @Override + public void afterPropertiesSet() throws Exception { + this.getValueQuery = String.format(this.getValueQuery, this.tablePrefix); + this.getValueForUpdateQuery = String.format(this.getValueForUpdateQuery, this.tablePrefix); + this.replaceValueQuery = String.format(this.replaceValueQuery, this.tablePrefix); + this.replaceValueByKeyQuery = String.format(this.replaceValueByKeyQuery, this.tablePrefix); + this.removeValueQuery = String.format(this.removeValueQuery, this.tablePrefix); + this.putIfAbsentValueQuery = String.format(this.putIfAbsentValueQuery, this.tablePrefix, this.tablePrefix); + } + + /** + * Instantiate a {@link JdbcMetadataStore} using provided dataSource {@link DataSource}. + * @param dataSource a {@link DataSource} + */ + public JdbcMetadataStore(DataSource dataSource) { + this(new JdbcTemplate(dataSource)); + } + + /** + * Instantiate a {@link JdbcMetadataStore} using provided jdbcOperations {@link JdbcOperations}. + * @param jdbcOperations a {@link JdbcOperations} + */ + public JdbcMetadataStore(JdbcOperations jdbcOperations) { + Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null"); + this.jdbcTemplate = jdbcOperations; + } + + /** + * Public setter for the table prefix property. This will be prefixed to all the table names before queries are + * executed. Defaults to {@link #DEFAULT_TABLE_PREFIX}. + * + * @param tablePrefix the tablePrefix to set + */ + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + /** + * A unique grouping identifier for all messages persisted with this store. Using multiple regions allows the store + * to be partitioned (if necessary) for different purposes. Defaults to DEFAULT. + * + * @param region the region name to set + */ + public void setRegion(String region) { + Assert.hasText(region, "Region must not be null or empty."); + this.region = region; + } + + @Override + @Transactional + public String putIfAbsent(String key, String value) { + Assert.notNull(key, "'key' cannot be null"); + Assert.notNull(value, "'value' cannot be null"); + while (true) { + //try to insert if does not exists + int affectedRows = tryToPutIfAbsent(key, value); + if (affectedRows > 0) { + //it was not in the table, so we have just inserted it + return null; + } + else { + //value should be in table. try to return it + try { + return this.jdbcTemplate.queryForObject(this.getValueQuery, String.class, key, this.region); + } + catch (EmptyResultDataAccessException e) { + //somebody deleted it between calls. try to insert again (go to beginning of while loop) + } + } + } + } + + private int tryToPutIfAbsent(String key, String value) { + return this.jdbcTemplate.update(this.putIfAbsentValueQuery, ps -> { + ps.setString(1, key); + ps.setString(2, value); + ps.setString(3, this.region); + ps.setString(4, key); + ps.setString(5, this.region); + }); + } + + @Override + @Transactional + public boolean replace(String key, String oldValue, String newValue) { + Assert.notNull(key, "'key' cannot be null"); + Assert.notNull(oldValue, "'oldValue' cannot be null"); + Assert.notNull(newValue, "'newValue' cannot be null"); + int affectedRows = this.jdbcTemplate.update(this.replaceValueQuery, ps -> { + ps.setString(1, newValue); + ps.setString(2, key); + ps.setString(3, oldValue); + ps.setString(4, this.region); + }); + return affectedRows > 0; + } + + @Override + @Transactional + public void put(String key, String value) { + Assert.notNull(key, "'key' cannot be null"); + Assert.notNull(value, "'value' cannot be null"); + while (true) { + //try to insert if does not exist, if exists we will try to update it + int affectedRows = tryToPutIfAbsent(key, value); + if (affectedRows == 0) { + //since value is not inserted, means it is already present + try { + //lock row for updating + this.jdbcTemplate.queryForObject(this.getValueForUpdateQuery, String.class, key, this.region); + } + catch (EmptyResultDataAccessException e) { + //if there are no rows with this key, somebody deleted it in between two calls + continue; //try to insert again from beginning + } + //lock successful, so - replace + this.jdbcTemplate.update(this.replaceValueByKeyQuery, ps -> { + ps.setString(1, value); + ps.setString(2, key); + ps.setString(3, this.region); + }); + } + return; + } + } + + @Override + @Transactional + public String get(String key) { + Assert.notNull(key, "'key' cannot be null"); + try { + return this.jdbcTemplate.queryForObject(this.getValueQuery, String.class, key, this.region); + } + catch (EmptyResultDataAccessException e) { + //if there are no rows with this key, return null + return null; + } + } + + @Override + @Transactional + public String remove(String key) { + Assert.notNull(key, "'key' cannot be null"); + String oldValue; + try { + //select old value and lock row for removal + oldValue = this.jdbcTemplate.queryForObject(this.getValueForUpdateQuery, String.class, key, this.region); + } + catch (EmptyResultDataAccessException e) { + //key is not present, so no need to delete it + return null; + } + //delete row and return old value + int updated = this.jdbcTemplate.update(this.removeValueQuery, key, this.region); + if (updated != 0) { + return oldValue; + } + return null; + } +} diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/package-info.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/package-info.java new file mode 100644 index 0000000000..9c34c86fc1 --- /dev/null +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains JDBC implementation of MetadataStore + */ +package org.springframework.integration.jdbc.metadata; diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-db2.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-db2.sql index f7df8fb7b6..477d808db6 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-db2.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-db2.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-derby.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-derby.sql index ccc16c1dfc..0a69f37da4 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-derby.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-derby.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-db2.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-db2.sql index 6805ae91d3..a548cca409 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-db2.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-db2.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; +DROP TABLE INT_METADATA_STORE ; DROP SEQUENCE INT_MESSAGE_SEQ ; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-derby.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-derby.sql index d49ada18b6..ad8d79d1af 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-derby.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-derby.sql @@ -8,4 +8,4 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; - +DROP TABLE INT_METADATA_STORE ; diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-h2.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-h2.sql index 8725447fe2..77e648a444 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-h2.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-h2.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP IF EXISTS; DROP TABLE INT_GROUP_TO_MESSAGE IF EXISTS; DROP TABLE INT_LOCK IF EXISTS; DROP TABLE INT_CHANNEL_MESSAGE IF EXISTS; +DROP TABLE INT_METADATA_STORE IF EXISTS; DROP SEQUENCE INT_MESSAGE_SEQ IF EXISTS; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-hsqldb.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-hsqldb.sql index 8725447fe2..77e648a444 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-hsqldb.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-hsqldb.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP IF EXISTS; DROP TABLE INT_GROUP_TO_MESSAGE IF EXISTS; DROP TABLE INT_LOCK IF EXISTS; DROP TABLE INT_CHANNEL_MESSAGE IF EXISTS; +DROP TABLE INT_METADATA_STORE IF EXISTS; DROP SEQUENCE INT_MESSAGE_SEQ IF EXISTS; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-mysql.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-mysql.sql index 35858e3de6..aa45c554ca 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-mysql.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-mysql.sql @@ -5,4 +5,4 @@ DROP TABLE IF EXISTS INT_MESSAGE_GROUP ; DROP TABLE IF EXISTS INT_GROUP_TO_MESSAGE ; DROP TABLE IF EXISTS INT_LOCK ; DROP TABLE IF EXISTS INT_CHANNEL_MESSAGE ; - +DROP TABLE IF EXISTS INT_METADATA_STORE ; diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-oracle.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-oracle.sql index 6805ae91d3..a548cca409 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-oracle.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-oracle.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; +DROP TABLE INT_METADATA_STORE ; DROP SEQUENCE INT_MESSAGE_SEQ ; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-postgresql.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-postgresql.sql index 6805ae91d3..a548cca409 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-postgresql.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-postgresql.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; +DROP TABLE INT_METADATA_STORE ; DROP SEQUENCE INT_MESSAGE_SEQ ; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sqlserver.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sqlserver.sql index 6805ae91d3..a548cca409 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sqlserver.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sqlserver.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; +DROP TABLE INT_METADATA_STORE ; DROP SEQUENCE INT_MESSAGE_SEQ ; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sybase.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sybase.sql index 6805ae91d3..a548cca409 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sybase.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-drop-sybase.sql @@ -8,5 +8,5 @@ DROP TABLE INT_MESSAGE_GROUP ; DROP TABLE INT_GROUP_TO_MESSAGE ; DROP TABLE INT_LOCK ; DROP TABLE INT_CHANNEL_MESSAGE ; +DROP TABLE INT_METADATA_STORE ; DROP SEQUENCE INT_MESSAGE_SEQ ; - diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-h2.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-h2.sql index 298dbcabbc..bbe50da7b3 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-h2.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-h2.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-hsqldb.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-hsqldb.sql index 7b74fbe416..1b9f443709 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-hsqldb.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-hsqldb.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-mysql.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-mysql.sql index 123fd295f1..a6f3587375 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-mysql.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-mysql.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +) ENGINE=InnoDB; diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-oracle.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-oracle.sql index 9eeef49c25..eda21929a7 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-oracle.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-oracle.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR2(255) NOT NULL, + METADATA_VALUE VARCHAR2(4000), + REGION VARCHAR2(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-postgresql.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-postgresql.sql index 97d4f69c56..c11e03cb71 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-postgresql.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-postgresql.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sqlserver.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sqlserver.sql index 7741ef3e15..6d5bfff80e 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sqlserver.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sqlserver.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sybase.sql b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sybase.sql index 65f2048c54..5863509f79 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sybase.sql +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/schema-sybase.sql @@ -51,3 +51,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY VARCHAR(255) NOT NULL, + METADATA_VALUE VARCHAR(4000), + REGION VARCHAR(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +) LOCK DATAROWS; diff --git a/spring-integration-jdbc/src/main/sql/destroy.sql.vpp b/spring-integration-jdbc/src/main/sql/destroy.sql.vpp index 8fa27afe22..d32c506917 100644 --- a/spring-integration-jdbc/src/main/sql/destroy.sql.vpp +++ b/spring-integration-jdbc/src/main/sql/destroy.sql.vpp @@ -12,7 +12,7 @@ DROP TABLE $!{IFEXISTSBEFORE} INT_MESSAGE_GROUP $!{IFEXISTS}; DROP TABLE $!{IFEXISTSBEFORE} INT_GROUP_TO_MESSAGE $!{IFEXISTS}; DROP TABLE $!{IFEXISTSBEFORE} INT_LOCK $!{IFEXISTS}; DROP TABLE $!{IFEXISTSBEFORE} INT_CHANNEL_MESSAGE $!{IFEXISTS}; +DROP TABLE $!{IFEXISTSBEFORE} INT_METADATA_STORE $!{IFEXISTS}; #if(${INT_MESSAGE_SEQ}) DROP SEQUENCE INT_MESSAGE_SEQ $!{IFEXISTS}; #end - diff --git a/spring-integration-jdbc/src/main/sql/schema.sql.vpp b/spring-integration-jdbc/src/main/sql/schema.sql.vpp index 9707e3855c..950a790bf2 100644 --- a/spring-integration-jdbc/src/main/sql/schema.sql.vpp +++ b/spring-integration-jdbc/src/main/sql/schema.sql.vpp @@ -52,3 +52,11 @@ CREATE TABLE INT_CHANNEL_MESSAGE ( CREATE INDEX INT_CHANNEL_MSG_DATE_IDX ON INT_CHANNEL_MESSAGE (CREATED_DATE, MESSAGE_SEQUENCE); CREATE INDEX INT_CHANNEL_MSG_PRIORITY_IDX ON INT_CHANNEL_MESSAGE (MESSAGE_PRIORITY DESC, CREATED_DATE, MESSAGE_SEQUENCE); + + +CREATE TABLE INT_METADATA_STORE ( + METADATA_KEY ${VARCHAR}(255) NOT NULL, + METADATA_VALUE ${VARCHAR}(4000), + REGION ${VARCHAR}(100) NOT NULL, + constraint METADATA_STORE primary key (METADATA_KEY, REGION) +)#if(${VOODOO}) ${VOODOO}#end; diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests-context.xml new file mode 100644 index 0000000000..83887861fc --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests.java new file mode 100644 index 0000000000..8556a3375f --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStoreTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed 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.springframework.integration.jdbc.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import javax.sql.DataSource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Bojan Vukasovic + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext // close at the end after class +@Transactional +public class JdbcMetadataStoreTests { + + @Autowired + private DataSource dataSource; + + + private JdbcMetadataStore metadataStore; + + @Before + public void init() throws Exception { + metadataStore = new JdbcMetadataStore(dataSource); + metadataStore.afterPropertiesSet(); + } + + @Test + public void keyAndValuesArePreservedOnPut() { + metadataStore.put("foo", "bar"); + metadataStore.put("foo", "bar1"); + metadataStore.put("foo2", "bar2"); + String bar1 = metadataStore.get("foo"); + String bar2 = metadataStore.get("foo2"); + assertEquals("bar1", bar1); + assertEquals("bar2", bar2); + } + + @Test + public void keyAndValuesAreNotPreservedOnRemove() { + metadataStore.put("foo", "bar"); + metadataStore.put("foo2", "bar2"); + metadataStore.remove("foo"); + String bar = metadataStore.get("foo"); + metadataStore.remove("foo2"); + String bar2 = metadataStore.get("foo2"); + assertNull(bar); + assertNull(bar2); + } + + @Test + public void keyAndValuesAreNotOverwrittenOnPutIfAbsent() { + metadataStore.put("foo", "bar"); + metadataStore.putIfAbsent("foo", "bar1"); + String bar = metadataStore.get("foo"); + assertEquals("bar", bar); + } + + @Test + public void nonExistentKeyIsNotRemoved() { + metadataStore.remove("non-existent"); + String ne = metadataStore.get("non-existent"); + assertNull(ne); + } + + @Test + public void existingKeyValueIsReplacedWithNewValueWhenOldValueMatches() { + metadataStore.put("foo", "bar"); + metadataStore.replace("foo", "bar", "bar2"); + String bar2 = metadataStore.get("foo"); + assertEquals("bar2", bar2); + } + + @Test + public void existingKeyValueIsNotReplacedWithNewValueWhenOldValueDoesNotMatch() { + metadataStore.put("foo", "bar"); + metadataStore.replace("foo", "bar1", "bar2"); + String bar = metadataStore.get("foo"); + assertEquals("bar", bar); + } +}