├── src ├── main │ ├── resources │ │ └── version.txt │ └── java │ │ └── com │ │ └── contrastsecurity │ │ └── cassandra │ │ └── migration │ │ ├── api │ │ ├── JavaMigration.java │ │ ├── MigrationChecksumProvider.java │ │ └── MigrationInfoProvider.java │ │ ├── CassandraMigrationException.java │ │ ├── config │ │ ├── MigrationType.java │ │ ├── Keyspace.java │ │ ├── ScriptsLocations.java │ │ ├── Cluster.java │ │ ├── ScriptsLocation.java │ │ └── MigrationConfigs.java │ │ ├── action │ │ ├── Initialize.java │ │ └── Validate.java │ │ ├── utils │ │ ├── CachePrepareStatement.java │ │ ├── scanner │ │ │ ├── classpath │ │ │ │ ├── DefaultUrlResolver.java │ │ │ │ ├── UrlResolver.java │ │ │ │ ├── ClassPathLocationScanner.java │ │ │ │ ├── FileSystemClassPathLocationScanner.java │ │ │ │ ├── JarFileClassPathLocationScanner.java │ │ │ │ └── ClassPathResource.java │ │ │ ├── Resource.java │ │ │ ├── Scanner.java │ │ │ └── filesystem │ │ │ │ ├── FileSystemResource.java │ │ │ │ └── FileSystemScanner.java │ │ ├── StopWatch.java │ │ ├── TimeFormat.java │ │ ├── DateUtils.java │ │ ├── ObjectUtils.java │ │ ├── VersionPrinter.java │ │ ├── Pair.java │ │ ├── UrlUtils.java │ │ ├── FeatureDetector.java │ │ ├── FileCopyUtils.java │ │ └── ClassUtils.java │ │ ├── logging │ │ ├── LogCreator.java │ │ ├── Log.java │ │ ├── slf4j │ │ │ ├── Slf4jLogCreator.java │ │ │ └── Slf4jLog.java │ │ ├── javautil │ │ │ ├── JavaUtilLogCreator.java │ │ │ └── JavaUtilLog.java │ │ ├── apachecommons │ │ │ ├── ApacheCommonsLogCreator.java │ │ │ └── ApacheCommonsLog.java │ │ ├── console │ │ │ ├── ConsoleLogCreator.java │ │ │ └── ConsoleLog.java │ │ └── LogFactory.java │ │ ├── resolver │ │ ├── MigrationExecutor.java │ │ ├── MigrationResolver.java │ │ ├── ResolvedMigrationComparator.java │ │ ├── java │ │ │ ├── JavaMigrationExecutor.java │ │ │ └── JavaMigrationResolver.java │ │ ├── cql │ │ │ ├── CqlMigrationExecutor.java │ │ │ └── CqlMigrationResolver.java │ │ └── MigrationInfoHelper.java │ │ ├── script │ │ └── Delimiter.java │ │ ├── CommandLine.java │ │ └── info │ │ ├── MigrationInfoContext.java │ │ ├── MigrationInfoDumper.java │ │ ├── ResolvedMigration.java │ │ └── MigrationVersion.java └── test │ ├── resources │ ├── com │ │ └── contrastsecurity │ │ │ └── cassandra │ │ │ └── migration │ │ │ └── utils │ │ │ └── scanner │ │ │ └── classpath │ │ │ ├── utf8.nofilter │ │ │ └── utf8bom.nofilter │ └── migration │ │ ├── outoforder │ │ └── V1_1_1__Late_arrival.cql │ │ ├── subdir │ │ ├── V1_1__Populate_table.cql │ │ ├── dir2 │ │ │ └── V2_0__Add_contents_table.cql │ │ └── dir1 │ │ │ └── V1__First.cql │ │ ├── integ_outoforder │ │ ├── V1_1_1__Late_arrival.cql │ │ ├── V1_0_0__First.cql │ │ └── V2_0_0__Second.cql │ │ ├── integ_outoforder2 │ │ ├── V1_1_1__Late_arrival.cql │ │ ├── V1_1_2__Late_arrival2.cql │ │ ├── V1_0_0__First.cql │ │ └── V2_0_0__Second.cql │ │ ├── integ_outoforder3 │ │ ├── V1_1_1__Late_arrival.cql │ │ ├── V1_1_3__Late_arrival3.cql │ │ ├── V1_1_2__Late_arrival2.cql │ │ ├── V1_0_0__First.cql │ │ └── V2_0_0__Second.cql │ │ ├── cql │ │ ├── V2_0__Add_contents_table.cql │ │ ├── V1__First.cql │ │ └── V1_2__Populate_table.cql │ │ └── integ │ │ ├── V1_0_0__First.cql │ │ └── V2_0_0__Second.cql │ └── java │ ├── migration │ └── integ │ │ └── java │ │ ├── V3_0__Third.java │ │ └── V3_0_1__Three_zero_one.java │ └── com │ └── contrastsecurity │ └── cassandra │ └── migration │ ├── config │ ├── KeyspaceTest.java │ ├── ClusterTest.java │ ├── ScriptsLocationTest.java │ └── ScriptsLocationsTest.java │ ├── resolver │ ├── java │ │ ├── dummy │ │ │ ├── V2__InterfaceBasedMigration.java │ │ │ ├── V4__DummyExtendedAbstractJdbcMigration.java │ │ │ ├── SabotageEnum.java │ │ │ ├── DummyAbstractJavaMigration.java │ │ │ └── Version3dot5.java │ │ ├── error │ │ │ └── BrokenJdbcMigration.java │ │ └── JavaMigrationResolverTest.java │ ├── cql │ │ └── CqlMigrationResolverTest.java │ ├── MigrationInfoHelperTest.java │ └── CompositeMigrationResolverTest.java │ ├── logging │ ├── StringLogCreator.java │ └── StringLog.java │ ├── utils │ ├── UrlUtilsTest.java │ ├── FeatureDetectorTest.java │ ├── ClassUtilsTest.java │ ├── scanner │ │ ├── filesystem │ │ │ └── FileSystemResourceTest.java │ │ └── classpath │ │ │ ├── ClassPathResourceTest.java │ │ │ └── FileSystemLocationScannerTest.java │ ├── TimeFormatTest.java │ └── StringUtilsTest.java │ ├── info │ ├── MigrationInfoTest.java │ └── MigrationInfoDumperTest.java │ └── BaseIT.java ├── .gitignore ├── maven-central-deploy.sh ├── LICENSE.txt ├── LICENSE_Flyway.txt └── security.md /src/main/resources/version.txt: -------------------------------------------------------------------------------- 1 | ${pom.version} -------------------------------------------------------------------------------- /src/test/resources/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/utf8.nofilter: -------------------------------------------------------------------------------- 1 | SELECT * FROM contents; -------------------------------------------------------------------------------- /src/test/resources/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/utf8bom.nofilter: -------------------------------------------------------------------------------- 1 | SELECT * FROM contents; -------------------------------------------------------------------------------- /src/test/resources/migration/outoforder/V1_1_1__Late_arrival.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'admin', 'adam'); -------------------------------------------------------------------------------- /src/test/resources/migration/subdir/V1_1__Populate_table.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test (space, key, value) VALUES ('myspace', 'mykey', 'myvalue'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder/V1_1_1__Late_arrival.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'admin', 'adam'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder2/V1_1_1__Late_arrival.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'admin', 'adam'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder3/V1_1_1__Late_arrival.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'admin', 'adam'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder3/V1_1_3__Late_arrival3.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'designer', 'dan'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder2/V1_1_2__Late_arrival2.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'developer', 'devin'); -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder3/V1_1_2__Late_arrival2.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test1 (space, key, value) VALUES ('users', 'developer', 'devin'); -------------------------------------------------------------------------------- /src/test/resources/migration/cql/V2_0__Add_contents_table.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); -------------------------------------------------------------------------------- /src/test/resources/migration/cql/V1__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); -------------------------------------------------------------------------------- /src/test/resources/migration/subdir/dir2/V2_0__Add_contents_table.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); -------------------------------------------------------------------------------- /src/test/resources/migration/subdir/dir1/V1__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); -------------------------------------------------------------------------------- /src/test/resources/migration/cql/V1_2__Populate_table.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO test (space, key, value) VALUES ('myspace1', 'mykey1', 'myvalue1'); 2 | INSERT INTO test (space, key, value) VALUES ('myspace2', 'mykey2', 'myvalue2'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | *.ipr 11 | 12 | # Maven 13 | *.log 14 | target/ 15 | output/ 16 | logs/ 17 | bin/ 18 | 19 | # Mac 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/api/JavaMigration.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.api; 2 | 3 | import com.datastax.driver.core.Session; 4 | 5 | public interface JavaMigration { 6 | void migrate(Session session) throws Exception; 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/migration/integ/V1_0_0__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test1 ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); 7 | 8 | INSERT INTO test1 (space, key, value) VALUES ('foo', 'blah', 'meh'); 9 | 10 | UPDATE test1 SET value = 'profit!' WHERE space = 'foo' AND key = 'blah'; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder/V1_0_0__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test1 ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); 7 | 8 | INSERT INTO test1 (space, key, value) VALUES ('foo', 'blah', 'meh'); 9 | 10 | UPDATE test1 SET value = 'profit!' WHERE space = 'foo' AND key = 'blah'; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder2/V1_0_0__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test1 ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); 7 | 8 | INSERT INTO test1 (space, key, value) VALUES ('foo', 'blah', 'meh'); 9 | 10 | UPDATE test1 SET value = 'profit!' WHERE space = 'foo' AND key = 'blah'; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder3/V1_0_0__First.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test1 ( 2 | space text, 3 | key text, 4 | value text, 5 | PRIMARY KEY (space, key) 6 | ) with CLUSTERING ORDER BY (key ASC); 7 | 8 | INSERT INTO test1 (space, key, value) VALUES ('foo', 'blah', 'meh'); 9 | 10 | UPDATE test1 SET value = 'profit!' WHERE space = 'foo' AND key = 'blah'; -------------------------------------------------------------------------------- /maven-central-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Deploy maven artefact in current directory into Maven central repository 3 | # using maven-release-plugin goals 4 | 5 | read -p "Really deploy to maven cetral repository (yes/no)? " 6 | 7 | if ( [ "$REPLY" == "yes" ] ) then 8 | mvn -P release release:clean release:prepare release:perform -B -e | tee maven-central-deploy.log 9 | else 10 | echo 'Exit without deploy' 11 | fi 12 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/CassandraMigrationException.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration; 2 | 3 | public class CassandraMigrationException extends RuntimeException { 4 | 5 | public CassandraMigrationException(String message) { 6 | super(message); 7 | } 8 | 9 | public CassandraMigrationException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/MigrationType.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | public enum MigrationType { 4 | /** 5 | * The type for the schema creation migration. 6 | */ 7 | SCHEMA, 8 | 9 | /** 10 | * The type for the metadata baseline migration. 11 | */ 12 | BASELINE, 13 | 14 | /** 15 | * The type for the CQL migration. 16 | */ 17 | CQL, 18 | 19 | /** 20 | * The type for Java driver migration 21 | */ 22 | JAVA_DRIVER 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/migration/integ/V2_0_0__Second.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); 7 | 8 | CREATE TABLE messages ( 9 | id bigint PRIMARY KEY, 10 | contents_id bigint, 11 | type int, 12 | delivered timestamp, 13 | organization_id bigint, 14 | application_id text, 15 | first_name text, 16 | last_name text, 17 | email_address text 18 | ); 19 | 20 | INSERT INTO contents (id, title, message) VALUES (1, 'foo', 'bar'); 21 | 22 | UPDATE contents SET message = 'meh' WHERE id = 1; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder/V2_0_0__Second.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); 7 | 8 | CREATE TABLE messages ( 9 | id bigint PRIMARY KEY, 10 | contents_id bigint, 11 | type int, 12 | delivered timestamp, 13 | organization_id bigint, 14 | application_id text, 15 | first_name text, 16 | last_name text, 17 | email_address text 18 | ); 19 | 20 | INSERT INTO contents (id, title, message) VALUES (1, 'foo', 'bar'); 21 | 22 | UPDATE contents SET message = 'meh' WHERE id = 1; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder2/V2_0_0__Second.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); 7 | 8 | CREATE TABLE messages ( 9 | id bigint PRIMARY KEY, 10 | contents_id bigint, 11 | type int, 12 | delivered timestamp, 13 | organization_id bigint, 14 | application_id text, 15 | first_name text, 16 | last_name text, 17 | email_address text 18 | ); 19 | 20 | INSERT INTO contents (id, title, message) VALUES (1, 'foo', 'bar'); 21 | 22 | UPDATE contents SET message = 'meh' WHERE id = 1; -------------------------------------------------------------------------------- /src/test/resources/migration/integ_outoforder3/V2_0_0__Second.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE contents ( 2 | id bigint PRIMARY KEY, 3 | title text, 4 | message text, 5 | created timestamp 6 | ); 7 | 8 | CREATE TABLE messages ( 9 | id bigint PRIMARY KEY, 10 | contents_id bigint, 11 | type int, 12 | delivered timestamp, 13 | organization_id bigint, 14 | application_id text, 15 | first_name text, 16 | last_name text, 17 | email_address text 18 | ); 19 | 20 | INSERT INTO contents (id, title, message) VALUES (1, 'foo', 'bar'); 21 | 22 | UPDATE contents SET message = 'meh' WHERE id = 1; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2015 Contrast Security 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE_Flyway.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010-2015 Axel Fontaine 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/action/Initialize.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.action; 2 | 3 | import com.contrastsecurity.cassandra.migration.config.Keyspace; 4 | import com.contrastsecurity.cassandra.migration.dao.SchemaVersionDAO; 5 | import com.datastax.driver.core.Session; 6 | 7 | public class Initialize { 8 | 9 | public void run(Session session, Keyspace keyspace, String migrationVersionTableName) { 10 | SchemaVersionDAO dao = new SchemaVersionDAO(session, keyspace, migrationVersionTableName); 11 | dao.createTablesIfNotExist(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/migration/integ/java/V3_0__Third.java: -------------------------------------------------------------------------------- 1 | package migration.integ.java; 2 | 3 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 4 | import com.datastax.driver.core.Session; 5 | import com.datastax.driver.core.querybuilder.Insert; 6 | import com.datastax.driver.core.querybuilder.QueryBuilder; 7 | 8 | public class V3_0__Third implements JavaMigration { 9 | 10 | @Override 11 | public void migrate(Session session) throws Exception { 12 | Insert insert = QueryBuilder.insertInto("test1"); 13 | insert.value("space", "web"); 14 | insert.value("key", "google"); 15 | insert.value("value", "google.com"); 16 | 17 | session.execute(insert); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/migration/integ/java/V3_0_1__Three_zero_one.java: -------------------------------------------------------------------------------- 1 | package migration.integ.java; 2 | 3 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 4 | import com.datastax.driver.core.Session; 5 | import com.datastax.driver.core.querybuilder.Insert; 6 | import com.datastax.driver.core.querybuilder.QueryBuilder; 7 | 8 | public class V3_0_1__Three_zero_one implements JavaMigration { 9 | @Override 10 | public void migrate(Session session) throws Exception { 11 | Insert insert = QueryBuilder.insertInto("test1"); 12 | insert.value("space", "web"); 13 | insert.value("key", "facebook"); 14 | insert.value("value", "facebook.com"); 15 | 16 | session.execute(insert); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/config/KeyspaceTest.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.*; 7 | 8 | public class KeyspaceTest { 9 | @Test 10 | public void shouldDefaultToNoKeyspaceButCanBeOverridden() { 11 | assertThat(new Keyspace().getName(), is(nullValue())); 12 | System.setProperty(Keyspace.KeyspaceProperty.NAME.getName(), "myspace"); 13 | assertThat(new Keyspace().getName(), is("myspace")); 14 | } 15 | 16 | @Test 17 | public void shouldHaveDefaultClusterObject() { 18 | assertThat(new Keyspace().getCluster(), is(notNullValue())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/CachePrepareStatement.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.utils; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | 5 | import com.datastax.driver.core.PreparedStatement; 6 | import com.datastax.driver.core.Session; 7 | 8 | public class CachePrepareStatement { 9 | private ConcurrentHashMap cacheStatement = new ConcurrentHashMap<>(); 10 | 11 | private Session session; 12 | 13 | public CachePrepareStatement(Session session) { 14 | this.session = session; 15 | } 16 | 17 | public PreparedStatement prepare(String s){ 18 | PreparedStatement ps = cacheStatement.get(s.hashCode()); 19 | if(ps == null){ 20 | ps = session.prepare(s); 21 | cacheStatement.put(s.hashCode(), ps); 22 | } 23 | return ps; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/LogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging; 17 | 18 | public interface LogCreator { 19 | Log createLogger(Class clazz); 20 | } 21 | -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | Contrast takes security vulnerabilities seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, please see our official [Vulnerability Disclosure Policy 6 | ](https://www.contrastsecurity.com/disclosure-policy) 7 | 8 | Contrast will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 9 | 10 | Report security bugs in third-party modules to the person or team maintaining the module. 11 | 12 | ## Learning More About Security 13 | 14 | To learn more about securing your applications with Contrast, please see the [our docs](https://docs.contrastsecurity.com/?lang=en). 15 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/MigrationExecutor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.datastax.driver.core.Session; 19 | 20 | /** 21 | * Executes a migration. 22 | */ 23 | public interface MigrationExecutor { 24 | void execute(Session session); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/MigrationResolver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 19 | 20 | import java.util.Collection; 21 | 22 | public interface MigrationResolver { 23 | Collection resolveMigrations(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/Log.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging; 17 | 18 | public interface Log { 19 | void debug(String message); 20 | 21 | void info(String message); 22 | 23 | void warn(String message); 24 | 25 | void error(String message); 26 | 27 | void error(String message, Exception e); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/DefaultUrlResolver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | 21 | /** 22 | * Default implementation of UrlResolver. 23 | */ 24 | public class DefaultUrlResolver implements UrlResolver { 25 | public URL toStandardJavaUrl(URL url) throws IOException { 26 | return url; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/StopWatch.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | public class StopWatch { 19 | 20 | private long start; 21 | private long stop; 22 | 23 | public void start() { 24 | start = System.currentTimeMillis(); 25 | } 26 | 27 | public void stop() { 28 | stop = System.currentTimeMillis(); 29 | } 30 | 31 | public long getTotalTimeMillis() { 32 | return stop - start; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/dummy/V2__InterfaceBasedMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.dummy; 17 | 18 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 19 | import com.datastax.driver.core.Session; 20 | 21 | /** 22 | * Test migration. 23 | */ 24 | public class V2__InterfaceBasedMigration implements JavaMigration { 25 | public void migrate(Session session) throws Exception { 26 | //Do nothing. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/ResolvedMigrationComparator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 19 | 20 | import java.util.Comparator; 21 | 22 | public class ResolvedMigrationComparator implements Comparator { 23 | @Override 24 | public int compare(ResolvedMigration o1, ResolvedMigration o2) { 25 | return o1.getVersion().compareTo(o2.getVersion()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/logging/StringLogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging; 17 | 18 | /** 19 | * Log creator for capturing the output as a string. 20 | */ 21 | public class StringLogCreator implements LogCreator { 22 | private final StringBuilder output = new StringBuilder(); 23 | 24 | public Log createLogger(Class clazz) { 25 | return new StringLog(output, false); 26 | } 27 | 28 | public String getOutput() { 29 | return output.toString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/slf4j/Slf4jLogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.slf4j; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogCreator; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | * Log Creator for Slf4j. 24 | */ 25 | public class Slf4jLogCreator implements LogCreator { 26 | public Log createLogger(Class clazz) { 27 | return new Slf4jLog(LoggerFactory.getLogger(clazz)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/UrlUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import org.junit.Test; 19 | 20 | import java.io.File; 21 | import java.net.MalformedURLException; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | 25 | public class UrlUtilsTest { 26 | @Test 27 | public void toFilePath() throws MalformedURLException { 28 | File file = new File("/test dir/a+b"); 29 | assertEquals(file.getAbsolutePath(), UrlUtils.toFilePath(file.toURI().toURL())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/dummy/V4__DummyExtendedAbstractJdbcMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.dummy; 17 | 18 | import com.datastax.driver.core.Session; 19 | 20 | /** 21 | * Test class that extends and abstract class instead of implementing JdbcMigration directly. 22 | */ 23 | public class V4__DummyExtendedAbstractJdbcMigration extends DummyAbstractJavaMigration { 24 | @Override 25 | public void doMigrate(Session session) throws Exception { 26 | // DO nothing 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/dummy/SabotageEnum.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.dummy; 17 | 18 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 19 | import com.datastax.driver.core.Session; 20 | 21 | /** 22 | * Trips up the ClassPathScanner. See issue 801. 23 | */ 24 | @SuppressWarnings("UnusedDeclaration") 25 | public enum SabotageEnum implements JavaMigration { 26 | FAIL { 27 | @Override 28 | public void migrate(Session session) throws Exception { 29 | 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/java/JavaMigrationExecutor.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.resolver.java; 2 | 3 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 4 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 5 | import com.contrastsecurity.cassandra.migration.resolver.MigrationExecutor; 6 | import com.datastax.driver.core.Session; 7 | 8 | /** 9 | * Adapter for executing migrations implementing JavaMigration. 10 | */ 11 | public class JavaMigrationExecutor implements MigrationExecutor { 12 | /** 13 | * The JavaMigration to execute. 14 | */ 15 | private final JavaMigration javaMigration; 16 | 17 | /** 18 | * Creates a new JdbcMigrationExecutor. 19 | * 20 | * @param javaMigration The JdbcMigration to execute. 21 | */ 22 | public JavaMigrationExecutor(JavaMigration javaMigration) { 23 | this.javaMigration = javaMigration; 24 | } 25 | 26 | @Override 27 | public void execute(Session session) { 28 | try { 29 | javaMigration.migrate(session); 30 | } catch (Exception e) { 31 | throw new CassandraMigrationException("Migration failed !", e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/api/MigrationChecksumProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.api; 17 | 18 | /** 19 | * Migration implementors that also implement this interface will be able to specify their checksum (for 20 | * validation), instead of having it automatically computed or default to {@code null} (for Java Migrations). 21 | */ 22 | public interface MigrationChecksumProvider { 23 | /** 24 | * Computes the checksum of the migration. 25 | * 26 | * @return The checksum of the migration. 27 | */ 28 | Integer getChecksum(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/javautil/JavaUtilLogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.javautil; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogCreator; 20 | 21 | import java.util.logging.Logger; 22 | 23 | /** 24 | * Log Creator for java.util.logging. 25 | */ 26 | public class JavaUtilLogCreator implements LogCreator { 27 | public Log createLogger(Class clazz) { 28 | return new JavaUtilLog(Logger.getLogger(clazz.getName())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/apachecommons/ApacheCommonsLogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.apachecommons; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogCreator; 20 | import org.apache.commons.logging.LogFactory; 21 | 22 | /** 23 | * Log Creator for Apache Commons Logging. 24 | */ 25 | public class ApacheCommonsLogCreator implements LogCreator { 26 | public Log createLogger(Class clazz) { 27 | return new ApacheCommonsLog(LogFactory.getLog(clazz)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/dummy/DummyAbstractJavaMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.dummy; 17 | 18 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 19 | import com.datastax.driver.core.Session; 20 | 21 | /** 22 | * Test for abstract class support. 23 | */ 24 | public abstract class DummyAbstractJavaMigration implements JavaMigration { 25 | public final void migrate(Session session) throws Exception { 26 | doMigrate(session); 27 | } 28 | 29 | public abstract void doMigrate(Session session) throws Exception; 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/error/BrokenJdbcMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.error; 17 | 18 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 19 | import com.datastax.driver.core.Session; 20 | 21 | /** 22 | * Test for exception in constructor support. 23 | */ 24 | public class BrokenJdbcMigration implements JavaMigration { 25 | public BrokenJdbcMigration() { 26 | throw new IllegalStateException("Expected!"); 27 | } 28 | 29 | public final void migrate(Session session) throws Exception { 30 | // Do nothing 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/UrlResolver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | 21 | /** 22 | * Resolves container-specific URLs into standard Java URLs. 23 | */ 24 | public interface UrlResolver { 25 | /** 26 | * Resolves this container-specific URL into standard Java URL. 27 | * 28 | * @param url The URL to resolve. 29 | * @return The matching standard Java URL. 30 | * @throws IOException when the scanning failed. 31 | */ 32 | URL toStandardJavaUrl(URL url) throws IOException; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/console/ConsoleLogCreator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.console; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogCreator; 20 | import com.contrastsecurity.cassandra.migration.logging.console.ConsoleLog.Level; 21 | 22 | public class ConsoleLogCreator implements LogCreator { 23 | private final Level level; 24 | 25 | public ConsoleLogCreator(Level level) { 26 | this.level = level; 27 | } 28 | 29 | public Log createLogger(Class clazz) { 30 | return new ConsoleLog(level); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/TimeFormat.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | /** 19 | * Formats execution times. 20 | */ 21 | public class TimeFormat { 22 | /** 23 | * Prevent instantiation. 24 | */ 25 | private TimeFormat() { 26 | // Do nothing 27 | } 28 | 29 | /** 30 | * Formats this execution time. 31 | * 32 | * @param millis The number of millis. 33 | * @return The execution in a human-readable format. 34 | */ 35 | public static String format(long millis) { 36 | return String.format("%02d:%02d.%03ds", millis / 60000, (millis % 60000) / 1000, (millis % 1000)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/FeatureDetectorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.hamcrest.MatcherAssert.assertThat; 21 | import static org.hamcrest.Matchers.is; 22 | 23 | public class FeatureDetectorTest { 24 | @Test 25 | public void shouldDetectSlf4j() { 26 | assertThat(new FeatureDetector(Thread.currentThread().getContextClassLoader()).isSlf4jAvailable(), is(true)); 27 | } 28 | 29 | @Test 30 | public void shouldDetectCommonsLogging() { 31 | assertThat(new FeatureDetector(Thread.currentThread().getContextClassLoader()).isApacheCommonsLoggingAvailable(), is(true)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/ClassUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | /** 24 | * Test for ClassUtils. 25 | */ 26 | public class ClassUtilsTest { 27 | @Test 28 | public void isPresent() { 29 | assertTrue(ClassUtils.isPresent("com.contrastsecurity.cassandra.migration.CassandraMigration", Thread.currentThread().getContextClassLoader())); 30 | } 31 | 32 | @Test 33 | public void isPresentNot() { 34 | assertFalse(ClassUtils.isPresent("com.example.FakeClass", Thread.currentThread().getContextClassLoader())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/Keyspace.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | public class Keyspace { 4 | private static final String PROPERTY_PREFIX = "cassandra.migration.keyspace."; 5 | 6 | public enum KeyspaceProperty { 7 | NAME(PROPERTY_PREFIX + "name", "Name of Cassandra keyspace"); 8 | 9 | private String name; 10 | private String description; 11 | 12 | KeyspaceProperty(String name, String description) { 13 | this.name = name; 14 | this.description = description; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public String getDescription() { 22 | return description; 23 | } 24 | } 25 | 26 | private Cluster cluster; 27 | private String name; 28 | 29 | public Keyspace() { 30 | cluster = new Cluster(); 31 | String keyspaceP = System.getProperty(KeyspaceProperty.NAME.getName()); 32 | if (null != keyspaceP && keyspaceP.trim().length() != 0) 33 | this.name = keyspaceP; 34 | } 35 | 36 | public Cluster getCluster() { 37 | return cluster; 38 | } 39 | 40 | public void setCluster(Cluster cluster) { 41 | this.cluster = cluster; 42 | } 43 | 44 | public String getName() { 45 | return name; 46 | } 47 | 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import java.text.SimpleDateFormat; 19 | import java.util.Date; 20 | 21 | /** 22 | * Utility methods for dealing with dates. 23 | */ 24 | public class DateUtils { 25 | /** 26 | * Prevents instantiation. 27 | */ 28 | private DateUtils() { 29 | // Do nothing 30 | } 31 | 32 | /** 33 | * Formats this date in the standard ISO format. 34 | * 35 | * @param date The date to format. 36 | * @return The date in ISO format. An empty string if the date is null. 37 | */ 38 | public static String formatDateAsIsoString(Date date) { 39 | if (date == null) { 40 | return ""; 41 | } 42 | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/api/MigrationInfoProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.api; 17 | 18 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 19 | 20 | /** 21 | * Migration implementors that also implement this interface will be able to specify their version and description 22 | * manually, instead of having it automatically computed. 23 | */ 24 | public interface MigrationInfoProvider { 25 | /** 26 | * Returns the version after the migration is complete. 27 | * 28 | * @return The version after the migration is complete. Never {@code null}. 29 | */ 30 | MigrationVersion getVersion(); 31 | 32 | /** 33 | * Returns the description for the migration history. 34 | * 35 | * @return The description for the migration history. Never {@code null}. 36 | */ 37 | String getDescription(); 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/scanner/filesystem/FileSystemResourceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.filesystem; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | public class FileSystemResourceTest { 23 | @Test 24 | public void getFilename() throws Exception { 25 | assertEquals("Mig777__Test.cql", new FileSystemResource("Mig777__Test.cql").getFilename()); 26 | assertEquals("Mig777__Test.cql", new FileSystemResource("folder/Mig777__Test.cql").getFilename()); 27 | } 28 | 29 | @Test 30 | public void getPath() throws Exception { 31 | assertEquals("Mig777__Test.cql", new FileSystemResource("Mig777__Test.cql").getLocation()); 32 | assertEquals("folder/Mig777__Test.cql", new FileSystemResource("folder/Mig777__Test.cql").getLocation()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/ClassPathLocationScanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import java.io.IOException; 19 | import java.net.URL; 20 | import java.util.Set; 21 | 22 | /** 23 | * Scans for classpath resources in this location. 24 | */ 25 | public interface ClassPathLocationScanner { 26 | /** 27 | * Finds the resource names below this location on the classpath under this locationUrl. 28 | * 29 | * @param location The system-independent location on the classpath. 30 | * @param locationUrl The system-specific physical location URL. 31 | * @return The system-independent names of the resources on the classpath. 32 | * @throws IOException when the scanning failed. 33 | */ 34 | Set findResourceNames(String location, URL locationUrl) throws IOException; 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/TimeFormatTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | /** 23 | * Small test for TimeFormat 24 | */ 25 | public class TimeFormatTest { 26 | @Test 27 | public void format() { 28 | assertEquals("00:00.001s", TimeFormat.format(1)); 29 | assertEquals("00:00.012s", TimeFormat.format(12)); 30 | assertEquals("00:00.123s", TimeFormat.format(123)); 31 | assertEquals("00:01.234s", TimeFormat.format(1234)); 32 | assertEquals("00:12.345s", TimeFormat.format(12345)); 33 | assertEquals("01:23.456s", TimeFormat.format(60000 + 23456)); 34 | assertEquals("12:34.567s", TimeFormat.format((60000 * 12) + 34567)); 35 | assertEquals("123:45.678s", TimeFormat.format((60000 * 123) + 45678)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/ObjectUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | /** 19 | * Collection of utility methods for dealing with objects. 20 | */ 21 | public class ObjectUtils { 22 | /** 23 | * Determine if the given objects are equal, returning {@code true} 24 | * if both are {@code null} or {@code false} if only one is 25 | * {@code null}. 26 | * 27 | * @param o1 first Object to compare 28 | * @param o2 second Object to compare 29 | * @return whether the given objects are equal 30 | */ 31 | @SuppressWarnings("SimplifiableIfStatement") 32 | public static boolean nullSafeEquals(Object o1, Object o2) { 33 | if (o1 == o2) { 34 | return true; 35 | } 36 | if (o1 == null || o2 == null) { 37 | return false; 38 | } 39 | return o1.equals(o2); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/cql/CqlMigrationExecutor.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.resolver.cql; 2 | 3 | import com.contrastsecurity.cassandra.migration.resolver.MigrationExecutor; 4 | import com.contrastsecurity.cassandra.migration.script.CqlScript; 5 | import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; 6 | import com.datastax.driver.core.Session; 7 | 8 | /** 9 | * Database migration based on a cql file. 10 | */ 11 | public class CqlMigrationExecutor implements MigrationExecutor { 12 | 13 | /** 14 | * The Resource pointing to the cql script. 15 | * The complete cql script is not held as a member field here because this would use the total size of all 16 | * cql migrations files in heap space during db migration. 17 | */ 18 | private final Resource cqlScriptResource; 19 | 20 | /** 21 | * The encoding of the cql script. 22 | */ 23 | private final String encoding; 24 | 25 | /** 26 | * Creates a new cql script migration based on this cql script. 27 | * 28 | * @param cqlScriptResource The resource containing the cql script. 29 | * @param encoding The encoding of this Cql migration. 30 | */ 31 | public CqlMigrationExecutor(Resource cqlScriptResource, String encoding) { 32 | this.cqlScriptResource = cqlScriptResource; 33 | this.encoding = encoding; 34 | } 35 | 36 | @Override 37 | public void execute(Session session) { 38 | CqlScript cqlScript = new CqlScript(cqlScriptResource, encoding); 39 | cqlScript.execute(session); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/config/ClusterTest.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.nullValue; 8 | 9 | public class ClusterTest { 10 | @Test 11 | public void shouldHaveDefaultConfigValues() { 12 | Cluster cluster = new Cluster(); 13 | assertThat(cluster.getContactpoints()[0], is("localhost")); 14 | assertThat(cluster.getPort(), is(9042)); 15 | assertThat(cluster.getUsername(), is(nullValue())); 16 | assertThat(cluster.getPassword(), is(nullValue())); 17 | } 18 | 19 | @Test 20 | public void systemPropsShouldOverrideDefaultConfigValues() { 21 | System.setProperty(Cluster.ClusterProperty.CONTACTPOINTS.getName(), "192.168.0.1,192.168.0.2, 192.168.0.3"); 22 | System.setProperty(Cluster.ClusterProperty.PORT.getName(), "9144"); 23 | System.setProperty(Cluster.ClusterProperty.USERNAME.getName(), "user"); 24 | System.setProperty(Cluster.ClusterProperty.PASSWORD.getName(), "pass"); 25 | 26 | Cluster cluster = new Cluster(); 27 | assertThat(cluster.getContactpoints()[0], is("192.168.0.1")); 28 | assertThat(cluster.getContactpoints()[1], is("192.168.0.2")); 29 | assertThat(cluster.getContactpoints()[2], is("192.168.0.3")); 30 | assertThat(cluster.getPort(), is(9144)); 31 | assertThat(cluster.getUsername(), is("user")); 32 | assertThat(cluster.getPassword(), is("pass")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/dummy/Version3dot5.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java.dummy; 17 | 18 | import com.contrastsecurity.cassandra.migration.api.MigrationChecksumProvider; 19 | import com.contrastsecurity.cassandra.migration.api.MigrationInfoProvider; 20 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 21 | import com.datastax.driver.core.Session; 22 | 23 | /** 24 | * Test migration. 25 | */ 26 | public class Version3dot5 extends DummyAbstractJavaMigration implements MigrationInfoProvider, MigrationChecksumProvider { 27 | public void doMigrate(Session session) throws Exception { 28 | //Do nothing. 29 | } 30 | 31 | public Integer getChecksum() { 32 | return 35; 33 | } 34 | 35 | public MigrationVersion getVersion() { 36 | return MigrationVersion.fromVersion("3.5"); 37 | } 38 | 39 | public String getDescription() { 40 | return "Three Dot Five"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/VersionPrinter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 20 | import com.contrastsecurity.cassandra.migration.utils.scanner.classpath.ClassPathResource; 21 | 22 | /** 23 | * Prints the Cassandra Migration version. 24 | */ 25 | public class VersionPrinter { 26 | private static final Log LOG = LogFactory.getLog(VersionPrinter.class); 27 | private static boolean printed; 28 | 29 | private VersionPrinter() { 30 | // Do nothing 31 | } 32 | 33 | public static void printVersion(ClassLoader classLoader) { 34 | if (printed) { 35 | return; 36 | } 37 | printed = true; 38 | String version = new ClassPathResource("version.txt", classLoader).loadAsString("UTF-8"); 39 | LOG.info("Cassandra Migration " + version + " by Contrast Security"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/Resource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner; 17 | 18 | public interface Resource { 19 | /** 20 | * @return The location of the resource on the classpath (path and filename). 21 | */ 22 | String getLocation(); 23 | 24 | /** 25 | * Retrieves the location of this resource on disk. 26 | * 27 | * @return The location of this resource on disk. 28 | */ 29 | String getLocationOnDisk(); 30 | 31 | /** 32 | * Loads this resource as a string. 33 | * 34 | * @param encoding The encoding to use. 35 | * @return The string contents of the resource. 36 | */ 37 | String loadAsString(String encoding); 38 | 39 | /** 40 | * Loads this resource as a byte array. 41 | * 42 | * @return The contents of the resource. 43 | */ 44 | byte[] loadAsBytes(); 45 | 46 | /** 47 | * @return The filename of this resource, without the path. 48 | */ 49 | String getFilename(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | /** 19 | * A simple pair of values. 20 | */ 21 | public class Pair { 22 | /** 23 | * The left side of the pair. 24 | */ 25 | private L left; 26 | 27 | /** 28 | * The right side of the pair. 29 | */ 30 | private R right; 31 | 32 | /** 33 | * Creates a new pair of these values. 34 | * 35 | * @param left The left side of the pair. 36 | * @param right The right side of the pair. 37 | * @return The pair. 38 | */ 39 | public static Pair of(L left, R right) { 40 | Pair pair = new Pair(); 41 | pair.left = left; 42 | pair.right = right; 43 | return pair; 44 | } 45 | 46 | /** 47 | * @return The left side of the pair. 48 | */ 49 | public L getLeft() { 50 | return left; 51 | } 52 | 53 | /** 54 | * @return The right side of the pair. 55 | */ 56 | public R getRight() { 57 | return right; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/slf4j/Slf4jLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.slf4j; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import org.slf4j.Logger; 20 | 21 | /** 22 | * Wrapper for a Slf4j logger. 23 | */ 24 | public class Slf4jLog implements Log { 25 | /** 26 | * Slf4j Logger. 27 | */ 28 | private final Logger logger; 29 | 30 | /** 31 | * Creates a new wrapper around this logger. 32 | * 33 | * @param logger The original Slf4j Logger. 34 | */ 35 | public Slf4jLog(Logger logger) { 36 | this.logger = logger; 37 | } 38 | 39 | public void debug(String message) { 40 | logger.debug(message); 41 | } 42 | 43 | public void info(String message) { 44 | logger.info(message); 45 | } 46 | 47 | public void warn(String message) { 48 | logger.warn(message); 49 | } 50 | 51 | public void error(String message) { 52 | logger.error(message); 53 | } 54 | 55 | public void error(String message, Exception e) { 56 | logger.error(message, e); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/logging/StringLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging; 17 | 18 | /** 19 | * Logger that captures output as a string. 20 | */ 21 | @SuppressWarnings("StringConcatenationInsideStringBufferAppend") 22 | public class StringLog implements Log { 23 | private final boolean debugEnabled; 24 | 25 | private final StringBuilder output; 26 | 27 | public StringLog(StringBuilder output, boolean debugEnabled) { 28 | this.output = output; 29 | this.debugEnabled = debugEnabled; 30 | } 31 | 32 | public void debug(String message) { 33 | if (debugEnabled) { 34 | output.append("DEBUG: " + message + "\n"); 35 | } 36 | } 37 | 38 | public void info(String message) { 39 | output.append("INFO: " + message + "\n"); 40 | } 41 | 42 | public void warn(String message) { 43 | output.append("WARN: " + message + "\n"); 44 | } 45 | 46 | public void error(String message) { 47 | output.append("ERROR: " + message + "\n"); 48 | } 49 | 50 | public void error(String message, Exception e) { 51 | output.append("ERROR: " + message + "\nCaused by: " + e); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/UrlUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import java.io.File; 19 | import java.io.UnsupportedEncodingException; 20 | import java.net.URL; 21 | import java.net.URLDecoder; 22 | 23 | /** 24 | * Collection of utility methods for working with URLs. 25 | */ 26 | public class UrlUtils { 27 | /** 28 | * Prevent instantiation. 29 | */ 30 | private UrlUtils() { 31 | // Do nothing 32 | } 33 | 34 | /** 35 | * Retrieves the file path of this URL, with any trailing slashes removed. 36 | * 37 | * @param url The URL to get the file path for. 38 | * @return The file path. 39 | */ 40 | public static String toFilePath(URL url) { 41 | try { 42 | String filePath = new File(URLDecoder.decode(url.getPath().replace("+", "%2b"), "UTF-8")).getAbsolutePath(); 43 | if (filePath.endsWith("/")) { 44 | return filePath.substring(0, filePath.length() - 1); 45 | } 46 | return filePath; 47 | } catch (UnsupportedEncodingException e) { 48 | throw new IllegalStateException("Can never happen", e); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/apachecommons/ApacheCommonsLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.apachecommons; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | 20 | /** 21 | * Wrapper for an Apache Commons Logging logger. 22 | */ 23 | public class ApacheCommonsLog implements Log { 24 | /** 25 | * Apache Commons Logging Logger. 26 | */ 27 | private final org.apache.commons.logging.Log logger; 28 | 29 | /** 30 | * Creates a new wrapper around this logger. 31 | * 32 | * @param logger The original Apache Commons Logging Logger. 33 | */ 34 | public ApacheCommonsLog(org.apache.commons.logging.Log logger) { 35 | this.logger = logger; 36 | } 37 | 38 | public void debug(String message) { 39 | logger.debug(message); 40 | } 41 | 42 | public void info(String message) { 43 | logger.info(message); 44 | } 45 | 46 | public void warn(String message) { 47 | logger.warn(message); 48 | } 49 | 50 | public void error(String message) { 51 | logger.error(message); 52 | } 53 | 54 | public void error(String message, Exception e) { 55 | logger.error(message, e); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/info/MigrationInfoTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 19 | import org.junit.Test; 20 | 21 | import static org.junit.Assert.assertTrue; 22 | 23 | public class MigrationInfoTest { 24 | @Test 25 | public void validate() { 26 | MigrationVersion version = MigrationVersion.fromVersion("1"); 27 | String description = "test"; 28 | String user = "testUser"; 29 | MigrationType type = MigrationType.CQL; 30 | 31 | ResolvedMigration resolvedMigration = new ResolvedMigration(); 32 | resolvedMigration.setVersion(version); 33 | resolvedMigration.setDescription(description); 34 | resolvedMigration.setType(type); 35 | resolvedMigration.setChecksum(456); 36 | 37 | AppliedMigration appliedMigration = new AppliedMigration(version, description, type, null, 123, user, 0, true); 38 | 39 | MigrationInfo migrationInfo = 40 | new MigrationInfo(resolvedMigration, appliedMigration, new MigrationInfoContext()); 41 | String message = migrationInfo.validate(); 42 | 43 | assertTrue(message.contains("123")); 44 | assertTrue(message.contains("456")); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/console/ConsoleLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.console; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | 20 | public class ConsoleLog implements Log { 21 | public static enum Level { 22 | DEBUG, INFO, WARN 23 | } 24 | 25 | private final Level level; 26 | 27 | /** 28 | * Creates a new Console Log. 29 | * 30 | * @param level the log level. 31 | */ 32 | public ConsoleLog(Level level) { 33 | this.level = level; 34 | } 35 | 36 | public void debug(String message) { 37 | if (level == Level.DEBUG) { 38 | System.out.println("DEBUG: " + message); 39 | } 40 | } 41 | 42 | public void info(String message) { 43 | if (level.compareTo(Level.INFO) <= 0) { 44 | System.out.println(message); 45 | } 46 | } 47 | 48 | public void warn(String message) { 49 | System.out.println("WARNING: " + message); 50 | } 51 | 52 | public void error(String message) { 53 | System.out.println("ERROR: " + message); 54 | } 55 | 56 | public void error(String message, Exception e) { 57 | System.out.println("ERROR: " + message); 58 | e.printStackTrace(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/FeatureDetector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | public class FeatureDetector { 19 | 20 | private ClassLoader classLoader; 21 | 22 | private Boolean slf4jAvailable; 23 | private Boolean apacheCommonsLoggingAvailable; 24 | 25 | public FeatureDetector(ClassLoader classLoader) { 26 | this.classLoader = classLoader; 27 | } 28 | 29 | public boolean isApacheCommonsLoggingAvailable() { 30 | if (apacheCommonsLoggingAvailable == null) { 31 | apacheCommonsLoggingAvailable = isPresent("org.apache.commons.logging.Log", classLoader); 32 | } 33 | 34 | return apacheCommonsLoggingAvailable; 35 | } 36 | 37 | public boolean isSlf4jAvailable() { 38 | if (slf4jAvailable == null) { 39 | slf4jAvailable = isPresent("org.slf4j.Logger", classLoader); 40 | } 41 | 42 | return slf4jAvailable; 43 | } 44 | 45 | private boolean isPresent(String className, ClassLoader classLoader) { 46 | try { 47 | classLoader.loadClass(className); 48 | return true; 49 | } catch (Throwable ex) { 50 | // Class or one of its dependencies is not present... 51 | return false; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/ClassPathResourceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | /** 23 | * Tests for ClassPathResource. 24 | */ 25 | public class ClassPathResourceTest { 26 | @Test 27 | public void getFilename() throws Exception { 28 | assertEquals("Mig777__Test.cql", new ClassPathResource("Mig777__Test.cql", Thread.currentThread().getContextClassLoader()).getFilename()); 29 | assertEquals("Mig777__Test.cql", new ClassPathResource("folder/Mig777__Test.cql", Thread.currentThread().getContextClassLoader()).getFilename()); 30 | } 31 | 32 | @Test 33 | public void loadAsStringUtf8WithoutBOM() { 34 | assertEquals("SELECT * FROM contents;", 35 | new ClassPathResource("com/contrastsecurity/cassandra/migration/utils/scanner/classpath/utf8.nofilter", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8")); 36 | } 37 | 38 | @Test 39 | public void loadAsStringUtf8WithBOM() { 40 | assertEquals("SELECT * FROM contents;", 41 | new ClassPathResource("com/contrastsecurity/cassandra/migration/utils/scanner/classpath/utf8bom.nofilter", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8")); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/action/Validate.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.action; 2 | 3 | import com.contrastsecurity.cassandra.migration.dao.SchemaVersionDAO; 4 | import com.contrastsecurity.cassandra.migration.info.MigrationInfoService; 5 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 6 | import com.contrastsecurity.cassandra.migration.logging.Log; 7 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 8 | import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; 9 | import com.contrastsecurity.cassandra.migration.utils.StopWatch; 10 | import com.contrastsecurity.cassandra.migration.utils.TimeFormat; 11 | 12 | /** 13 | * Validates the applied migrations against the available ones. 14 | */ 15 | public class Validate { 16 | 17 | /** 18 | * logging support 19 | */ 20 | private static final Log LOG = LogFactory.getLog(Validate.class); 21 | 22 | private SchemaVersionDAO schemaVersionDao; 23 | 24 | /** 25 | * migration resolver 26 | */ 27 | private MigrationResolver migrationResolver; 28 | 29 | /** 30 | * migration target 31 | */ 32 | private MigrationVersion migrationTarget; 33 | 34 | private boolean outOfOrder; 35 | 36 | private boolean pendingOrFuture; 37 | 38 | public Validate(MigrationResolver migrationResolver, SchemaVersionDAO schemaVersionDao, MigrationVersion migrationTarget, boolean outOfOrder, boolean pendingOrFuture) { 39 | this.schemaVersionDao = schemaVersionDao; 40 | this.migrationResolver = migrationResolver; 41 | this.migrationTarget = migrationTarget; 42 | this.outOfOrder = outOfOrder; 43 | this.pendingOrFuture = pendingOrFuture; 44 | } 45 | 46 | public String run() { 47 | StopWatch stopWatch = new StopWatch(); 48 | stopWatch.start(); 49 | 50 | MigrationInfoService infoService = new MigrationInfoService(migrationResolver, schemaVersionDao, migrationTarget, outOfOrder, pendingOrFuture); 51 | infoService.refresh(); 52 | int count = infoService.all().length; 53 | String validationError = infoService.validate(); 54 | 55 | stopWatch.stop(); 56 | 57 | LOG.info(String.format("Validated %d migrations (execution time %s)", count, TimeFormat.format(stopWatch.getTotalTimeMillis()))); 58 | 59 | return validationError; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/FileSystemLocationScannerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import com.contrastsecurity.cassandra.migration.utils.UrlUtils; 19 | import org.junit.Test; 20 | 21 | import java.io.File; 22 | import java.net.URL; 23 | import java.util.Set; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | 27 | /** 28 | * Test for FileSystemClassPathLocationScanner. 29 | */ 30 | public class FileSystemLocationScannerTest { 31 | @Test 32 | public void findResourceNamesFromFileSystem() throws Exception { 33 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 34 | String path = UrlUtils.toFilePath(classLoader.getResources("migration").nextElement()) + File.separator; 35 | 36 | Set resourceNames = 37 | new FileSystemClassPathLocationScanner().findResourceNamesFromFileSystem(path, "cql", new File(path, "cql")); 38 | 39 | assertEquals(3, resourceNames.size()); 40 | String[] names = resourceNames.toArray(new String[3]); 41 | assertEquals("cql/V1_2__Populate_table.cql", names[0]); 42 | assertEquals("cql/V1__First.cql", names[1]); 43 | assertEquals("cql/V2_0__Add_contents_table.cql", names[2]); 44 | } 45 | 46 | @Test 47 | public void findResourceNamesNonExistantPath() throws Exception { 48 | URL url = new URL("file", null, 0, "X:\\dummy\\cql"); 49 | 50 | Set resourceNames = 51 | new FileSystemClassPathLocationScanner().findResourceNames("cql", url); 52 | 53 | assertEquals(0, resourceNames.size()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/script/Delimiter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.script; 17 | 18 | /** 19 | * Represents a cql statement delimiter. 20 | */ 21 | public class Delimiter { 22 | /** 23 | * The actual delimiter string. 24 | */ 25 | private final String delimiter; 26 | 27 | /** 28 | * Whether the delimiter sits alone on a new line or not. 29 | */ 30 | private final boolean aloneOnLine; 31 | 32 | /** 33 | * Creates a new delimiter. 34 | * 35 | * @param delimiter The actual delimiter string. 36 | * @param aloneOnLine Whether the delimiter sits alone on a new line or not. 37 | */ 38 | public Delimiter(String delimiter, boolean aloneOnLine) { 39 | this.delimiter = delimiter; 40 | this.aloneOnLine = aloneOnLine; 41 | } 42 | 43 | /** 44 | * @return The actual delimiter string. 45 | */ 46 | public String getDelimiter() { 47 | return delimiter; 48 | } 49 | 50 | /** 51 | * @return Whether the delimiter sits alone on a new line or not. 52 | */ 53 | public boolean isAloneOnLine() { 54 | return aloneOnLine; 55 | } 56 | 57 | @Override 58 | public boolean equals(Object o) { 59 | if (this == o) return true; 60 | if (o == null || getClass() != o.getClass()) return false; 61 | 62 | Delimiter delimiter1 = (Delimiter) o; 63 | 64 | return aloneOnLine == delimiter1.aloneOnLine && delimiter.equals(delimiter1.delimiter); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | int result = delimiter.hashCode(); 70 | result = 31 * result + (aloneOnLine ? 1 : 0); 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/LogFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.apachecommons.ApacheCommonsLogCreator; 19 | import com.contrastsecurity.cassandra.migration.logging.javautil.JavaUtilLogCreator; 20 | import com.contrastsecurity.cassandra.migration.logging.slf4j.Slf4jLogCreator; 21 | import com.contrastsecurity.cassandra.migration.utils.FeatureDetector; 22 | 23 | public class LogFactory { 24 | /** 25 | * Factory for implementation-specific loggers. 26 | */ 27 | private static LogCreator logCreator; 28 | 29 | /** 30 | * Prevent instantiation. 31 | */ 32 | private LogFactory() { 33 | // Do nothing 34 | } 35 | 36 | /** 37 | * @param logCreator The factory for implementation-specific loggers. 38 | */ 39 | public static void setLogCreator(LogCreator logCreator) { 40 | LogFactory.logCreator = logCreator; 41 | } 42 | 43 | /** 44 | * Retrieves the matching logger for this class. 45 | * 46 | * @param clazz The class to get the logger for. 47 | * @return The logger. 48 | */ 49 | public static Log getLog(Class clazz) { 50 | if (logCreator == null) { 51 | FeatureDetector featureDetector = new FeatureDetector(Thread.currentThread().getContextClassLoader()); 52 | if (featureDetector.isSlf4jAvailable()) { 53 | logCreator = new Slf4jLogCreator(); 54 | } else if (featureDetector.isApacheCommonsLoggingAvailable()) { 55 | logCreator = new ApacheCommonsLogCreator(); 56 | } else { 57 | logCreator = new JavaUtilLogCreator(); 58 | } 59 | } 60 | 61 | return logCreator.createLogger(clazz); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/ScriptsLocations.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | import com.contrastsecurity.cassandra.migration.logging.Log; 4 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * The locations to scan recursively for migrations. 12 | * 13 | *

The location type is determined by its prefix. 14 | * Unprefixed locations or locations starting with {@code classpath:} point to a package on the classpath and may 15 | * contain both cql and java-based migrations. 16 | * Locations starting with {@code filesystem:} point to a directory on the filesystem and may only contain cql 17 | * migrations.

18 | * 19 | * (default: db/migration) 20 | */ 21 | public class ScriptsLocations { 22 | private static final Log LOG = LogFactory.getLog(ScriptsLocations.class); 23 | 24 | private final List locations = new ArrayList<>(); 25 | 26 | public ScriptsLocations(String... rawLocations) { 27 | 28 | List normalizedLocations = new ArrayList<>(); 29 | for (String rawLocation : rawLocations) { 30 | normalizedLocations.add(new ScriptsLocation(rawLocation)); 31 | } 32 | Collections.sort(normalizedLocations); 33 | 34 | for (ScriptsLocation normalizedLocation : normalizedLocations) { 35 | if (locations.contains(normalizedLocation)) { 36 | LOG.warn("Discarding duplicate location '" + normalizedLocation + "'"); 37 | continue; 38 | } 39 | 40 | ScriptsLocation parentLocation = getParentLocationIfExists(normalizedLocation, locations); 41 | if (parentLocation != null) { 42 | LOG.warn("Discarding location '" + normalizedLocation + "' as it is a sublocation of '" + parentLocation + "'"); 43 | continue; 44 | } 45 | 46 | locations.add(normalizedLocation); 47 | } 48 | } 49 | 50 | /** 51 | * @return The locations. 52 | */ 53 | public List getLocations() { 54 | return locations; 55 | } 56 | 57 | /** 58 | * Retrieves this location's parent within this list, if any. 59 | * 60 | * @param location The location to check. 61 | * @param finalLocations The list to search. 62 | * @return The parent location. {@code null} if none. 63 | */ 64 | private ScriptsLocation getParentLocationIfExists(ScriptsLocation location, List finalLocations) { 65 | for (ScriptsLocation finalLocation : finalLocations) { 66 | if (finalLocation.isParentOf(location)) { 67 | return finalLocation; 68 | } 69 | } 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/Cluster.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | public class Cluster { 4 | private static final String PROPERTY_PREFIX = "cassandra.migration.cluster."; 5 | 6 | public enum ClusterProperty { 7 | CONTACTPOINTS(PROPERTY_PREFIX + "contactpoints", "Comma separated values of node IP addresses"), 8 | PORT(PROPERTY_PREFIX + "port", "CQL native transport port"), 9 | USERNAME(PROPERTY_PREFIX + "username", "Username for password authenticator"), 10 | PASSWORD(PROPERTY_PREFIX + "password", "Password for password authenticator"); 11 | 12 | private String name; 13 | private String description; 14 | 15 | ClusterProperty(String name, String description) { 16 | this.name = name; 17 | this.description = description; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public String getDescription() { 25 | return description; 26 | } 27 | } 28 | 29 | private String[] contactpoints = {"localhost"}; 30 | private int port = 9042; 31 | private String username; 32 | private String password; 33 | 34 | public Cluster() { 35 | String contactpointsP = System.getProperty(ClusterProperty.CONTACTPOINTS.getName()); 36 | if (null != contactpointsP && contactpointsP.trim().length() != 0) 37 | this.contactpoints = contactpointsP.replaceAll("\\s+", "").split("[,]"); 38 | 39 | String portP = System.getProperty(ClusterProperty.PORT.getName()); 40 | if (null != portP && portP.trim().length() != 0) 41 | this.port = Integer.parseInt(portP); 42 | 43 | String usernameP = System.getProperty(ClusterProperty.USERNAME.getName()); 44 | if (null != usernameP && usernameP.trim().length() != 0) 45 | this.username = usernameP; 46 | 47 | String passwordP = System.getProperty(ClusterProperty.PASSWORD.getName()); 48 | if (null != passwordP && passwordP.trim().length() != 0) 49 | this.password = passwordP; 50 | } 51 | 52 | public String[] getContactpoints() { 53 | return contactpoints; 54 | } 55 | 56 | public void setContactpoints(String... contactpoints) { 57 | this.contactpoints = contactpoints; 58 | } 59 | 60 | public int getPort() { 61 | return port; 62 | } 63 | 64 | public void setPort(int port) { 65 | this.port = port; 66 | } 67 | 68 | public String getUsername() { 69 | return username; 70 | } 71 | 72 | public void setUsername(String username) { 73 | this.username = username; 74 | } 75 | 76 | public String getPassword() { 77 | return password; 78 | } 79 | 80 | public void setPassword(String password) { 81 | this.password = password; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/MigrationInfoHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 20 | import com.contrastsecurity.cassandra.migration.utils.Pair; 21 | 22 | /** 23 | * Parsing support for migrations that use the standard Flyway version + description embedding in their name. These 24 | * migrations have names like 1_2__Description . 25 | */ 26 | public class MigrationInfoHelper { 27 | /** 28 | * Prevents instantiation. 29 | */ 30 | private MigrationInfoHelper() { 31 | //Do nothing. 32 | } 33 | 34 | /** 35 | * Extracts the schema version and the description from a migration name formatted as 1_2__Description. 36 | * 37 | * @param migrationName The migration name to parse. Should not contain any folders or packages. 38 | * @param prefix The migration prefix. 39 | * @param separator The migration separator. 40 | * @param suffix The migration suffix. 41 | * @return The extracted schema version. 42 | * @throws CassandraMigrationException if the migration name does not follow the standard conventions. 43 | */ 44 | public static Pair extractVersionAndDescription(String migrationName, 45 | String prefix, String separator, String suffix) { 46 | String cleanMigrationName = migrationName.substring(prefix.length(), migrationName.length() - suffix.length()); 47 | 48 | // Handle the description 49 | int descriptionPos = cleanMigrationName.indexOf(separator); 50 | if (descriptionPos < 0) { 51 | throw new CassandraMigrationException("Wrong migration name format: " + migrationName 52 | + "(It should look like this: " + prefix + "1_2" + separator + "Description" + suffix + ")"); 53 | } 54 | 55 | String version = cleanMigrationName.substring(0, descriptionPos); 56 | String description = cleanMigrationName.substring(descriptionPos + separator.length()).replaceAll("_", " "); 57 | return Pair.of(MigrationVersion.fromVersion(version), description); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/utils/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.*; 21 | 22 | /** 23 | * Testcase for StringUtils. 24 | */ 25 | public class StringUtilsTest { 26 | @Test 27 | public void trimOrPad() { 28 | assertEquals("Hello World ", StringUtils.trimOrPad("Hello World", 15)); 29 | assertEquals("Hello Worl", StringUtils.trimOrPad("Hello World", 10)); 30 | assertEquals(" ", StringUtils.trimOrPad(null, 10)); 31 | } 32 | 33 | @Test 34 | public void isNumeric() { 35 | assertFalse(StringUtils.isNumeric(null)); 36 | assertTrue(StringUtils.isNumeric("")); 37 | assertFalse(StringUtils.isNumeric(" ")); 38 | assertTrue(StringUtils.isNumeric("123")); 39 | assertFalse(StringUtils.isNumeric("12 3")); 40 | assertFalse(StringUtils.isNumeric("ab2c")); 41 | assertFalse(StringUtils.isNumeric("12-3")); 42 | assertFalse(StringUtils.isNumeric("12.3")); 43 | } 44 | 45 | @Test 46 | public void collapseWhitespace() { 47 | assertEquals("", StringUtils.collapseWhitespace("")); 48 | assertEquals("abc", StringUtils.collapseWhitespace("abc")); 49 | assertEquals("a b", StringUtils.collapseWhitespace("a b")); 50 | assertEquals(" a ", StringUtils.collapseWhitespace(" a ")); 51 | assertEquals(" a ", StringUtils.collapseWhitespace(" a ")); 52 | assertEquals("a b", StringUtils.collapseWhitespace("a b")); 53 | assertEquals("a b c", StringUtils.collapseWhitespace("a b c")); 54 | assertEquals(" a b c ", StringUtils.collapseWhitespace(" a b c ")); 55 | } 56 | 57 | @Test 58 | public void tokenizeToStringArray() { 59 | assertArrayEquals(new String[]{"abc"}, StringUtils.tokenizeToStringArray("abc", ",")); 60 | assertArrayEquals(new String[]{"abc", "def"}, StringUtils.tokenizeToStringArray("abc,def", ",")); 61 | assertArrayEquals(new String[]{"abc", "def"}, StringUtils.tokenizeToStringArray(" abc ,def ", ",")); 62 | assertArrayEquals(new String[]{"", "abc"}, StringUtils.tokenizeToStringArray(",abc", ",")); 63 | assertArrayEquals(new String[]{"", "abc"}, StringUtils.tokenizeToStringArray(" , abc", ",")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/ScriptsLocation.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 4 | 5 | public final class ScriptsLocation implements Comparable { 6 | 7 | private static final String CLASSPATH_PREFIX = "classpath:"; 8 | public static final String FILESYSTEM_PREFIX = "filesystem:"; 9 | 10 | private String prefix; //classpath or filesystem 11 | private String path; 12 | 13 | public ScriptsLocation(String descriptor) { 14 | String normalizedDescriptor = descriptor.trim().replace("\\", "/"); 15 | 16 | if (normalizedDescriptor.contains(":")) { 17 | prefix = normalizedDescriptor.substring(0, normalizedDescriptor.indexOf(":") + 1); 18 | path = normalizedDescriptor.substring(normalizedDescriptor.indexOf(":") + 1); 19 | } else { 20 | prefix = CLASSPATH_PREFIX; 21 | path = normalizedDescriptor; 22 | } 23 | 24 | if (isClassPath()) { 25 | path = path.replace(".", "/"); 26 | if (path.startsWith("/")) { 27 | path = path.substring(1); 28 | } 29 | } else { 30 | if (!isFileSystem()) { 31 | throw new CassandraMigrationException("Unknown prefix for location. " + 32 | "Must be " + CLASSPATH_PREFIX + " or " + FILESYSTEM_PREFIX + "." 33 | + normalizedDescriptor); 34 | } 35 | } 36 | 37 | if (path.endsWith("/")) { 38 | path = path.substring(0, path.length() - 1); 39 | } 40 | } 41 | 42 | public boolean isClassPath() { 43 | return CLASSPATH_PREFIX.equals(prefix); 44 | } 45 | 46 | public boolean isFileSystem() { 47 | return FILESYSTEM_PREFIX.equals(prefix); 48 | } 49 | 50 | public boolean isParentOf(ScriptsLocation other) { 51 | return (other.getDescriptor() + "/").startsWith(getDescriptor() + "/"); 52 | } 53 | 54 | public String getPrefix() { 55 | return prefix; 56 | } 57 | 58 | public String getPath() { 59 | return path; 60 | } 61 | 62 | public String getDescriptor() { 63 | return prefix + path; 64 | } 65 | 66 | @SuppressWarnings("NullableProblems") 67 | public int compareTo(ScriptsLocation o) { 68 | return getDescriptor().compareTo(o.getDescriptor()); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (this == o) return true; 74 | if (o == null || getClass() != o.getClass()) return false; 75 | 76 | ScriptsLocation location = (ScriptsLocation) o; 77 | 78 | return getDescriptor().equals(location.getDescriptor()); 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return getDescriptor().hashCode(); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return getDescriptor(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/config/ScriptsLocationTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.config; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.*; 21 | 22 | /** 23 | * Test for location. 24 | */ 25 | public class ScriptsLocationTest { 26 | @Test 27 | public void defaultPrefix() { 28 | ScriptsLocation location = new ScriptsLocation("db/migration"); 29 | assertEquals("classpath:", location.getPrefix()); 30 | assertTrue(location.isClassPath()); 31 | assertEquals("db/migration", location.getPath()); 32 | assertEquals("classpath:db/migration", location.getDescriptor()); 33 | } 34 | 35 | @Test 36 | public void classpathPrefix() { 37 | ScriptsLocation location = new ScriptsLocation("classpath:db/migration"); 38 | assertEquals("classpath:", location.getPrefix()); 39 | assertTrue(location.isClassPath()); 40 | assertEquals("db/migration", location.getPath()); 41 | assertEquals("classpath:db/migration", location.getDescriptor()); 42 | } 43 | 44 | @Test 45 | public void filesystemPrefix() { 46 | ScriptsLocation location = new ScriptsLocation("filesystem:db/migration"); 47 | assertEquals("filesystem:", location.getPrefix()); 48 | assertFalse(location.isClassPath()); 49 | assertEquals("db/migration", location.getPath()); 50 | assertEquals("filesystem:db/migration", location.getDescriptor()); 51 | } 52 | 53 | @Test 54 | public void filesystemPrefixAbsolutePath() { 55 | ScriptsLocation location = new ScriptsLocation("filesystem:/db/migration"); 56 | assertEquals("filesystem:", location.getPrefix()); 57 | assertFalse(location.isClassPath()); 58 | assertEquals("/db/migration", location.getPath()); 59 | assertEquals("filesystem:/db/migration", location.getDescriptor()); 60 | } 61 | 62 | @Test 63 | public void filesystemPrefixWithDotsInPath() { 64 | ScriptsLocation location = new ScriptsLocation("filesystem:util-2.0.4/db/migration"); 65 | assertEquals("filesystem:", location.getPrefix()); 66 | assertFalse(location.isClassPath()); 67 | assertEquals("util-2.0.4/db/migration", location.getPath()); 68 | assertEquals("filesystem:util-2.0.4/db/migration", location.getDescriptor()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/CommandLine.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration; 2 | 3 | import com.contrastsecurity.cassandra.migration.config.Keyspace; 4 | import com.contrastsecurity.cassandra.migration.logging.Log; 5 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 6 | import com.contrastsecurity.cassandra.migration.logging.console.ConsoleLog; 7 | import com.contrastsecurity.cassandra.migration.logging.console.ConsoleLogCreator; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class CommandLine { 13 | 14 | /** 15 | * command to trigger migrate action 16 | */ 17 | public static final String MIGRATE = "migrate"; 18 | 19 | /** 20 | * command to trigger validate action 21 | */ 22 | public static final String VALIDATE = "validate"; 23 | 24 | /** 25 | * logging support 26 | */ 27 | private static Log LOG; 28 | 29 | /** 30 | * @param args 31 | * command line arguments 32 | */ 33 | public static void main(String[] args) { 34 | ConsoleLog.Level logLevel = getLogLevel(args); 35 | initLogging(logLevel); 36 | 37 | List operations = determineOperations(args); 38 | if (operations.isEmpty()) { 39 | printUsage(); 40 | return; 41 | } 42 | 43 | String operation = operations.get(0); 44 | 45 | CassandraMigration cm = new CassandraMigration(); 46 | Keyspace ks = new Keyspace(); 47 | cm.setKeyspace(ks); 48 | if (MIGRATE.equalsIgnoreCase(operation)) { 49 | cm.migrate(); 50 | } else if (VALIDATE.equalsIgnoreCase(operation)) { 51 | cm.validate(); 52 | } 53 | } 54 | 55 | private static List determineOperations(String[] args) { 56 | List operations = new ArrayList<>(); 57 | 58 | for (String arg : args) { 59 | if (!arg.startsWith("-")) { 60 | operations.add(arg); 61 | } 62 | } 63 | 64 | return operations; 65 | } 66 | 67 | static void initLogging(ConsoleLog.Level level) { 68 | LogFactory.setLogCreator(new ConsoleLogCreator(level)); 69 | LOG = LogFactory.getLog(CommandLine.class); 70 | } 71 | 72 | private static ConsoleLog.Level getLogLevel(String[] args) { 73 | for (String arg : args) { 74 | if ("-X".equals(arg)) { 75 | return ConsoleLog.Level.DEBUG; 76 | } 77 | if ("-q".equals(arg)) { 78 | return ConsoleLog.Level.WARN; 79 | } 80 | } 81 | return ConsoleLog.Level.INFO; 82 | } 83 | 84 | private static void printUsage() { 85 | LOG.info("********"); 86 | LOG.info("* Usage"); 87 | LOG.info("********"); 88 | LOG.info(""); 89 | LOG.info("cassandra-migration [options] command"); 90 | LOG.info(""); 91 | LOG.info("Commands"); 92 | LOG.info("========"); 93 | LOG.info("migrate : Migrates the database"); 94 | LOG.info("validate : Validates the applied migrations against the available ones"); 95 | LOG.info(""); 96 | LOG.info("Add -X to print debug output"); 97 | LOG.info("Add -q to suppress all output, except for errors and warnings"); 98 | LOG.info(""); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/BaseIT.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration; 2 | 3 | import com.contrastsecurity.cassandra.migration.config.Keyspace; 4 | import com.datastax.driver.core.Cluster; 5 | import com.datastax.driver.core.Session; 6 | import com.datastax.driver.core.SimpleStatement; 7 | import com.datastax.driver.core.Statement; 8 | import org.cassandraunit.utils.EmbeddedCassandraServerHelper; 9 | import org.junit.After; 10 | import org.junit.AfterClass; 11 | import org.junit.Before; 12 | import org.junit.BeforeClass; 13 | 14 | public abstract class BaseIT { 15 | public static final String CASSANDRA__KEYSPACE = "cassandra_migration_test"; 16 | public static final String CASSANDRA_CONTACT_POINT = "localhost"; 17 | public static final int CASSANDRA_PORT = 9147; 18 | public static final String CASSANDRA_USERNAME = "cassandra"; 19 | public static final String CASSANDRA_PASSWORD = "cassandra"; 20 | 21 | private Session session; 22 | 23 | @BeforeClass 24 | public static void beforeSuite() throws Exception { 25 | EmbeddedCassandraServerHelper.startEmbeddedCassandra( 26 | "cassandra-unit.yaml", 27 | "target/embeddedCassandra", 28 | 200000L); 29 | } 30 | 31 | @AfterClass 32 | public static void afterSuite() { 33 | EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); 34 | } 35 | 36 | @Before 37 | public void createKeyspace() { 38 | Statement statement = new SimpleStatement( 39 | "CREATE KEYSPACE " + CASSANDRA__KEYSPACE + 40 | " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };" 41 | ); 42 | getSession(getKeyspace()).execute(statement); 43 | } 44 | 45 | @After 46 | public void dropKeyspace() { 47 | Statement statement = new SimpleStatement( 48 | "DROP KEYSPACE " + CASSANDRA__KEYSPACE + ";" 49 | ); 50 | getSession(getKeyspace()).execute(statement); 51 | } 52 | 53 | protected Keyspace getKeyspace() { 54 | Keyspace ks = new Keyspace(); 55 | ks.setName(CASSANDRA__KEYSPACE); 56 | ks.getCluster().setContactpoints(CASSANDRA_CONTACT_POINT); 57 | ks.getCluster().setPort(CASSANDRA_PORT); 58 | ks.getCluster().setUsername(CASSANDRA_USERNAME); 59 | ks.getCluster().setPassword(CASSANDRA_PASSWORD); 60 | return ks; 61 | } 62 | 63 | private Session getSession(Keyspace keyspace) { 64 | if (session != null && !session.isClosed()) 65 | return session; 66 | 67 | com.datastax.driver.core.Cluster.Builder builder = new com.datastax.driver.core.Cluster.Builder(); 68 | builder.addContactPoints(CASSANDRA_CONTACT_POINT).withPort(CASSANDRA_PORT); 69 | builder.withCredentials(keyspace.getCluster().getUsername(), keyspace.getCluster().getPassword()); 70 | Cluster cluster = builder.build(); 71 | session = cluster.connect(); 72 | return session; 73 | } 74 | 75 | protected Session getSession() { 76 | return session; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/logging/javautil/JavaUtilLog.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.logging.javautil; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | 20 | import java.util.logging.Level; 21 | import java.util.logging.LogRecord; 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * Wrapper for a java.util.Logger. 26 | */ 27 | public class JavaUtilLog implements Log { 28 | /** 29 | * Java Util Logger. 30 | */ 31 | private final Logger logger; 32 | 33 | /** 34 | * Creates a new wrapper around this logger. 35 | * 36 | * @param logger The original java.util Logger. 37 | */ 38 | public JavaUtilLog(Logger logger) { 39 | this.logger = logger; 40 | } 41 | 42 | public void debug(String message) { 43 | log(Level.FINE, message, null); 44 | } 45 | 46 | public void info(String message) { 47 | log(Level.INFO, message, null); 48 | } 49 | 50 | public void warn(String message) { 51 | log(Level.WARNING, message, null); 52 | } 53 | 54 | public void error(String message) { 55 | log(Level.SEVERE, message, null); 56 | } 57 | 58 | public void error(String message, Exception e) { 59 | log(Level.SEVERE, message, e); 60 | } 61 | 62 | /** 63 | * Log the message at the specified level with the specified exception if any. 64 | * 65 | * @param level The level to log at. 66 | * @param message The message to log. 67 | * @param e The exception, if any. 68 | */ 69 | private void log(Level level, String message, Exception e) { 70 | // millis and thread are filled by the constructor 71 | LogRecord record = new LogRecord(level, message); 72 | record.setLoggerName(logger.getName()); 73 | record.setThrown(e); 74 | record.setSourceClassName(logger.getName()); 75 | record.setSourceMethodName(getMethodName()); 76 | logger.log(record); 77 | } 78 | 79 | /** 80 | * Computes the source method name for the log output. 81 | */ 82 | private String getMethodName() { 83 | StackTraceElement[] steArray = new Throwable().getStackTrace(); 84 | 85 | for (StackTraceElement stackTraceElement : steArray) { 86 | if (logger.getName().equals(stackTraceElement.getClassName())) { 87 | return stackTraceElement.getMethodName(); 88 | } 89 | } 90 | 91 | return null; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/info/MigrationInfoContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | /** 19 | * The current context of the migrations. 20 | */ 21 | public class MigrationInfoContext { 22 | /** 23 | * Whether out of order migrations are allowed. 24 | */ 25 | public boolean outOfOrder; 26 | 27 | /** 28 | * Whether pending or future migrations are allowed. 29 | */ 30 | public boolean pendingOrFuture; 31 | 32 | /** 33 | * The migration target. 34 | */ 35 | public MigrationVersion target; 36 | 37 | /** 38 | * The SCHEMA migration version that was applied. 39 | */ 40 | public MigrationVersion schema; 41 | 42 | /** 43 | * The BASELINE migration version that was applied. 44 | */ 45 | public MigrationVersion baseline; 46 | 47 | /** 48 | * The last resolved migration. 49 | */ 50 | public MigrationVersion lastResolved = MigrationVersion.EMPTY; 51 | 52 | /** 53 | * The last applied migration. 54 | */ 55 | public MigrationVersion lastApplied = MigrationVersion.EMPTY; 56 | 57 | @SuppressWarnings("SimplifiableIfStatement") 58 | @Override 59 | public boolean equals(Object o) { 60 | if (this == o) return true; 61 | if (o == null || getClass() != o.getClass()) return false; 62 | 63 | MigrationInfoContext context = (MigrationInfoContext) o; 64 | 65 | if (outOfOrder != context.outOfOrder) return false; 66 | if (pendingOrFuture != context.pendingOrFuture) return false; 67 | if (schema != null ? !schema.equals(context.schema) : context.schema != null) return false; 68 | if (baseline != null ? !baseline.equals(context.baseline) : context.baseline != null) return false; 69 | if (!lastApplied.equals(context.lastApplied)) return false; 70 | if (!lastResolved.equals(context.lastResolved)) return false; 71 | return target.equals(context.target); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | int result = (outOfOrder ? 1 : 0); 77 | result = 31 * result + (pendingOrFuture ? 1 : 0); 78 | result = 31 * result + target.hashCode(); 79 | result = 31 * result + (schema != null ? schema.hashCode() : 0); 80 | result = 31 * result + (baseline != null ? baseline.hashCode() : 0); 81 | result = 31 * result + lastResolved.hashCode(); 82 | result = 31 * result + lastApplied.hashCode(); 83 | return result; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/Scanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; 20 | import com.contrastsecurity.cassandra.migration.utils.scanner.classpath.ClassPathScanner; 21 | import com.contrastsecurity.cassandra.migration.utils.scanner.filesystem.FileSystemScanner; 22 | 23 | /** 24 | * Scanner for Resources and Classes. 25 | */ 26 | public class Scanner { 27 | private final ClassLoader classLoader; 28 | 29 | public Scanner(ClassLoader classLoader) { 30 | this.classLoader = classLoader; 31 | } 32 | 33 | /** 34 | * Scans this location for resources, starting with the specified prefix and ending with the specified suffix. 35 | * 36 | * @param location The location to start searching. Subdirectories are also searched. 37 | * @param prefix The prefix of the resource names to match. 38 | * @param suffix The suffix of the resource names to match. 39 | * @return The resources that were found. 40 | */ 41 | public Resource[] scanForResources(ScriptsLocation location, String prefix, String suffix) { 42 | try { 43 | if (location.isFileSystem()) { 44 | return new FileSystemScanner().scanForResources(location.getPath(), prefix, suffix); 45 | } 46 | 47 | return new ClassPathScanner(classLoader).scanForResources(location.getPath(), prefix, suffix); 48 | } catch (Exception e) { 49 | throw new CassandraMigrationException("Unable to scan for CQL migrations in location: " + location, e); 50 | } 51 | } 52 | 53 | 54 | /** 55 | * Scans the classpath for concrete classes under the specified package implementing this interface. 56 | * Non-instantiable abstract classes are filtered out. 57 | * 58 | * @param location The location (package) in the classpath to start scanning. 59 | * Subpackages are also scanned. 60 | * @param implementedInterface The interface the matching classes should implement. 61 | * @return The non-abstract classes that were found. 62 | * @throws Exception when the location could not be scanned. 63 | */ 64 | public Class[] scanForClasses(ScriptsLocation location, Class implementedInterface) throws Exception { 65 | return new ClassPathScanner(classLoader).scanForClasses(location.getPath(), implementedInterface); 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/info/MigrationInfoDumper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | import com.contrastsecurity.cassandra.migration.utils.DateUtils; 19 | import com.contrastsecurity.cassandra.migration.utils.StringUtils; 20 | 21 | /** 22 | * Dumps migrations in an ascii-art table in the logs and the console. 23 | */ 24 | public class MigrationInfoDumper { 25 | private static final String VERSION_TITLE = "Version"; 26 | private static final String DESCRIPTION_TITLE = "Description"; 27 | 28 | /** 29 | * Prevent instantiation. 30 | */ 31 | private MigrationInfoDumper() { 32 | // Do nothing 33 | } 34 | 35 | /** 36 | * Dumps the info about all migrations into an ascii table. 37 | * 38 | * @param migrationInfos The list of migrationInfos to dump. 39 | * @return The ascii table, as one big multi-line string. 40 | */ 41 | public static String dumpToAsciiTable(final MigrationInfo[] migrationInfos) { 42 | int versionWidth = VERSION_TITLE.length(); 43 | int descriptionWidth = DESCRIPTION_TITLE.length(); 44 | 45 | for (MigrationInfo migrationInfo : migrationInfos) { 46 | versionWidth = Math.max(versionWidth, migrationInfo.getVersion().toString().length()); 47 | descriptionWidth = Math.max(descriptionWidth, migrationInfo.getDescription().length()); 48 | } 49 | 50 | String ruler = "+-" + StringUtils.trimOrPad("", versionWidth, '-') 51 | + "-+-" + StringUtils.trimOrPad("", descriptionWidth, '-') + "-+---------------------+---------+\n"; 52 | 53 | StringBuilder table = new StringBuilder(); 54 | table.append(ruler); 55 | table.append("| ").append(StringUtils.trimOrPad(VERSION_TITLE, versionWidth, ' ')) 56 | .append(" | ").append(StringUtils.trimOrPad(DESCRIPTION_TITLE, descriptionWidth)) 57 | .append(" | Installed on | State |\n"); 58 | table.append(ruler); 59 | 60 | if (migrationInfos.length == 0) { 61 | table.append(StringUtils.trimOrPad("| No migrations found", ruler.length() - 2, ' ')).append("|\n"); 62 | } else { 63 | for (MigrationInfo migrationInfo : migrationInfos) { 64 | table.append("| ").append(StringUtils.trimOrPad(migrationInfo.getVersion().toString(), versionWidth)); 65 | table.append(" | ").append(StringUtils.trimOrPad(migrationInfo.getDescription(), descriptionWidth)); 66 | table.append(" | ").append(StringUtils.trimOrPad(DateUtils.formatDateAsIsoString(migrationInfo.getInstalledOn()), 19)); 67 | table.append(" | ").append(StringUtils.trimOrPad(migrationInfo.getState().getDisplayName(), 7)); 68 | table.append(" |\n"); 69 | } 70 | } 71 | 72 | table.append(ruler); 73 | return table.toString(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/cql/CqlMigrationResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.resolver.cql; 2 | 3 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 4 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; 5 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 6 | import com.contrastsecurity.cassandra.migration.utils.scanner.classpath.ClassPathResource; 7 | import com.contrastsecurity.cassandra.migration.utils.scanner.filesystem.FileSystemResource; 8 | import org.junit.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | 16 | /** 17 | * Testcase for CqlMigration. 18 | */ 19 | public class CqlMigrationResolverTest { 20 | @Test 21 | public void resolveMigrations() { 22 | CqlMigrationResolver cqlMigrationResolver = 23 | new CqlMigrationResolver(Thread.currentThread().getContextClassLoader(), 24 | new ScriptsLocation("migration/subdir"), "UTF-8"); 25 | Collection migrations = cqlMigrationResolver.resolveMigrations(); 26 | 27 | assertEquals(3, migrations.size()); 28 | 29 | List migrationList = new ArrayList(migrations); 30 | 31 | assertEquals("1", migrationList.get(0).getVersion().toString()); 32 | assertEquals("1.1", migrationList.get(1).getVersion().toString()); 33 | assertEquals("2.0", migrationList.get(2).getVersion().toString()); 34 | 35 | assertEquals("dir1/V1__First.cql", migrationList.get(0).getScript()); 36 | assertEquals("V1_1__Populate_table.cql", migrationList.get(1).getScript()); 37 | assertEquals("dir2/V2_0__Add_contents_table.cql", migrationList.get(2).getScript()); 38 | } 39 | 40 | @Test(expected = CassandraMigrationException.class) 41 | public void resolveMigrationsNonExisting() { 42 | CqlMigrationResolver cqlMigrationResolver = 43 | new CqlMigrationResolver(Thread.currentThread().getContextClassLoader(), 44 | new ScriptsLocation("non/existing"), "UTF-8"); 45 | 46 | cqlMigrationResolver.resolveMigrations(); 47 | } 48 | 49 | @Test 50 | public void extractScriptName() { 51 | CqlMigrationResolver cqlMigrationResolver = 52 | new CqlMigrationResolver(Thread.currentThread().getContextClassLoader(), 53 | new ScriptsLocation("db/migration"), "UTF-8"); 54 | 55 | assertEquals("db_0__init.cql", cqlMigrationResolver.extractScriptName( 56 | new ClassPathResource("db/migration/db_0__init.cql", Thread.currentThread().getContextClassLoader()))); 57 | } 58 | 59 | @Test 60 | public void extractScriptNameRootLocation() { 61 | CqlMigrationResolver cqlMigrationResolver = 62 | new CqlMigrationResolver(Thread.currentThread().getContextClassLoader(), new ScriptsLocation(""), "UTF-8"); 63 | 64 | assertEquals("db_0__init.cql", cqlMigrationResolver.extractScriptName( 65 | new ClassPathResource("db_0__init.cql", Thread.currentThread().getContextClassLoader()))); 66 | } 67 | 68 | @Test 69 | public void extractScriptNameFileSystemPrefix() { 70 | CqlMigrationResolver cqlMigrationResolver = 71 | new CqlMigrationResolver(Thread.currentThread().getContextClassLoader(), 72 | new ScriptsLocation("filesystem:/some/dir"), "UTF-8"); 73 | 74 | assertEquals("V3.171__patch.cql", cqlMigrationResolver.extractScriptName(new FileSystemResource("/some/dir/V3.171__patch.cql"))); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/filesystem/FileSystemResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.filesystem; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.utils.FileCopyUtils; 20 | import com.contrastsecurity.cassandra.migration.utils.StringUtils; 21 | import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; 22 | 23 | import java.io.*; 24 | import java.nio.charset.Charset; 25 | 26 | /** 27 | * A resource on the filesystem. 28 | */ 29 | public class FileSystemResource implements Resource, Comparable { 30 | /** 31 | * The location of the resource on the filesystem. 32 | */ 33 | private File location; 34 | 35 | /** 36 | * Creates a new ClassPathResource. 37 | * 38 | * @param location The location of the resource on the filesystem. 39 | */ 40 | public FileSystemResource(String location) { 41 | this.location = new File(location); 42 | } 43 | 44 | /** 45 | * @return The location of the resource on the classpath. 46 | */ 47 | public String getLocation() { 48 | return StringUtils.replaceAll(location.getPath(), "\\", "/"); 49 | } 50 | 51 | /** 52 | * Retrieves the location of this resource on disk. 53 | * 54 | * @return The location of this resource on disk. 55 | */ 56 | public String getLocationOnDisk() { 57 | return location.getAbsolutePath(); 58 | } 59 | 60 | /** 61 | * Loads this resource as a string. 62 | * 63 | * @param encoding The encoding to use. 64 | * @return The string contents of the resource. 65 | */ 66 | public String loadAsString(String encoding) { 67 | try { 68 | InputStream inputStream = new FileInputStream(location); 69 | Reader reader = new InputStreamReader(inputStream, Charset.forName(encoding)); 70 | 71 | return FileCopyUtils.copyToString(reader); 72 | } catch (IOException e) { 73 | throw new CassandraMigrationException("Unable to load filesystem resource: " + location.getPath() + " (encoding: " + encoding + ")", e); 74 | } 75 | } 76 | 77 | /** 78 | * Loads this resource as a byte array. 79 | * 80 | * @return The contents of the resource. 81 | */ 82 | public byte[] loadAsBytes() { 83 | try { 84 | InputStream inputStream = new FileInputStream(location); 85 | return FileCopyUtils.copyToByteArray(inputStream); 86 | } catch (IOException e) { 87 | throw new CassandraMigrationException("Unable to load filesystem resource: " + location.getPath(), e); 88 | } 89 | } 90 | 91 | /** 92 | * @return The filename of this resource, without the path. 93 | */ 94 | public String getFilename() { 95 | return location.getName(); 96 | } 97 | 98 | @SuppressWarnings("NullableProblems") 99 | public int compareTo(FileSystemResource o) { 100 | return location.compareTo(o.location); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/config/ScriptsLocationsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.config; 17 | 18 | import org.junit.Test; 19 | 20 | import java.util.Iterator; 21 | import java.util.List; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertTrue; 25 | 26 | /** 27 | * Small Test for Locations. 28 | */ 29 | public class ScriptsLocationsTest { 30 | @Test 31 | public void mergeLocations() { 32 | ScriptsLocations locations = new ScriptsLocations("db/locations", "db/files", "db/classes"); 33 | List locationList = locations.getLocations(); 34 | assertEquals(3, locationList.size()); 35 | Iterator iterator = locationList.iterator(); 36 | assertEquals("db/classes", iterator.next().getPath()); 37 | assertEquals("db/files", iterator.next().getPath()); 38 | assertEquals("db/locations", iterator.next().getPath()); 39 | } 40 | 41 | @Test 42 | public void mergeLocationsDuplicate() { 43 | ScriptsLocations locations = new ScriptsLocations("db/locations", "db/migration", "db/migration"); 44 | List locationList = locations.getLocations(); 45 | assertEquals(2, locationList.size()); 46 | Iterator iterator = locationList.iterator(); 47 | assertEquals("db/locations", iterator.next().getPath()); 48 | assertEquals("db/migration", iterator.next().getPath()); 49 | } 50 | 51 | @Test 52 | public void mergeLocationsOverlap() { 53 | ScriptsLocations locations = new ScriptsLocations("db/migration/oracle", "db/migration", "db/migration"); 54 | List locationList = locations.getLocations(); 55 | assertEquals(1, locationList.size()); 56 | assertEquals("db/migration", locationList.get(0).getPath()); 57 | } 58 | 59 | @Test 60 | public void mergeLocationsSimilarButNoOverlap() { 61 | ScriptsLocations locations = new ScriptsLocations("db/migration/oracle", "db/migration", "db/migrationtest"); 62 | List locationList = locations.getLocations(); 63 | assertEquals(2, locationList.size()); 64 | assertTrue(locationList.contains(new ScriptsLocation("db/migration"))); 65 | assertTrue(locationList.contains(new ScriptsLocation("db/migrationtest"))); 66 | } 67 | 68 | @Test 69 | public void mergeLocationsSimilarButNoOverlapCamelCase() { 70 | ScriptsLocations locations = new ScriptsLocations("/com/xxx/Star/", "/com/xxx/StarTrack/"); 71 | List locationList = locations.getLocations(); 72 | assertEquals(2, locationList.size()); 73 | assertTrue(locationList.contains(new ScriptsLocation("com/xxx/Star"))); 74 | assertTrue(locationList.contains(new ScriptsLocation("com/xxx/StarTrack"))); 75 | } 76 | 77 | @Test 78 | public void mergeLocationsSimilarButNoOverlapHyphen() { 79 | ScriptsLocations locations = new ScriptsLocations("db/migration/oracle", "db/migration", "db/migration-test"); 80 | List locationList = locations.getLocations(); 81 | assertEquals(2, locationList.size()); 82 | assertTrue(locationList.contains(new ScriptsLocation("db/migration"))); 83 | assertTrue(locationList.contains(new ScriptsLocation("db/migration-test"))); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/java/JavaMigrationResolverTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; 20 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 21 | import com.contrastsecurity.cassandra.migration.resolver.java.dummy.V2__InterfaceBasedMigration; 22 | import com.contrastsecurity.cassandra.migration.resolver.java.dummy.Version3dot5; 23 | import org.junit.Test; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.List; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertNull; 31 | 32 | /** 33 | * Test for JavaMigrationResolver. 34 | */ 35 | public class JavaMigrationResolverTest { 36 | @Test(expected = CassandraMigrationException.class) 37 | public void broken() { 38 | new JavaMigrationResolver(Thread.currentThread().getContextClassLoader(), new ScriptsLocation("com/contrastsecurity/cassandra/migration/resolver/java/error")).resolveMigrations(); 39 | } 40 | 41 | @Test 42 | public void resolveMigrations() { 43 | JavaMigrationResolver jdbcMigrationResolver = 44 | new JavaMigrationResolver(Thread.currentThread().getContextClassLoader(), new ScriptsLocation("com/contrastsecurity/cassandra/migration/resolver/java/dummy")); 45 | Collection migrations = jdbcMigrationResolver.resolveMigrations(); 46 | 47 | assertEquals(3, migrations.size()); 48 | 49 | List migrationList = new ArrayList(migrations); 50 | 51 | ResolvedMigration migrationInfo = migrationList.get(0); 52 | assertEquals("2", migrationInfo.getVersion().toString()); 53 | assertEquals("InterfaceBasedMigration", migrationInfo.getDescription()); 54 | assertNull(migrationInfo.getChecksum()); 55 | 56 | ResolvedMigration migrationInfo1 = migrationList.get(1); 57 | assertEquals("3.5", migrationInfo1.getVersion().toString()); 58 | assertEquals("Three Dot Five", migrationInfo1.getDescription()); 59 | assertEquals(35, migrationInfo1.getChecksum().intValue()); 60 | 61 | ResolvedMigration migrationInfo2 = migrationList.get(2); 62 | assertEquals("4", migrationInfo2.getVersion().toString()); 63 | } 64 | 65 | @Test 66 | public void conventionOverConfiguration() { 67 | JavaMigrationResolver jdbcMigrationResolver = new JavaMigrationResolver(Thread.currentThread().getContextClassLoader(), null); 68 | ResolvedMigration migrationInfo = jdbcMigrationResolver.extractMigrationInfo(new V2__InterfaceBasedMigration()); 69 | assertEquals("2", migrationInfo.getVersion().toString()); 70 | assertEquals("InterfaceBasedMigration", migrationInfo.getDescription()); 71 | assertNull(migrationInfo.getChecksum()); 72 | } 73 | 74 | @Test 75 | public void explicitInfo() { 76 | JavaMigrationResolver jdbcMigrationResolver = new JavaMigrationResolver(Thread.currentThread().getContextClassLoader(), null); 77 | ResolvedMigration migrationInfo = jdbcMigrationResolver.extractMigrationInfo(new Version3dot5()); 78 | assertEquals("3.5", migrationInfo.getVersion().toString()); 79 | assertEquals("Three Dot Five", migrationInfo.getDescription()); 80 | assertEquals(35, migrationInfo.getChecksum().intValue()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/info/MigrationInfoDumperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 19 | import com.contrastsecurity.cassandra.migration.dao.SchemaVersionDAO; 20 | import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; 21 | import com.contrastsecurity.cassandra.migration.utils.StringUtils; 22 | import org.junit.Test; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.when; 31 | 32 | /** 33 | * Test for MigrationInfoDumper. 34 | */ 35 | public class MigrationInfoDumperTest { 36 | @Test 37 | public void dumpEmpty() { 38 | String table = MigrationInfoDumper.dumpToAsciiTable(new MigrationInfo[0]); 39 | String[] lines = StringUtils.tokenizeToStringArray(table, "\n"); 40 | 41 | assertEquals(5, lines.length); 42 | for (String line : lines) { 43 | assertEquals(lines[0].length(), line.length()); 44 | } 45 | } 46 | 47 | @Test 48 | public void dump2pending() { 49 | MigrationInfoService migrationInfoService = 50 | new MigrationInfoService( 51 | createMigrationResolver(createAvailableMigration("1"), createAvailableMigration("2.2014.09.11.55.45613")), 52 | createSchemaVersionDAO(), MigrationVersion.LATEST, false, true); 53 | migrationInfoService.refresh(); 54 | 55 | String table = MigrationInfoDumper.dumpToAsciiTable(migrationInfoService.all()); 56 | String[] lines = StringUtils.tokenizeToStringArray(table, "\n"); 57 | 58 | assertEquals(6, lines.length); 59 | for (String line : lines) { 60 | assertEquals(lines[0].length(), line.length()); 61 | } 62 | } 63 | 64 | /** 65 | * Creates a new available migration with this version. 66 | * 67 | * @param version The version of the migration. 68 | * @return The available migration. 69 | */ 70 | private ResolvedMigration createAvailableMigration(String version) { 71 | ResolvedMigration migration = new ResolvedMigration(); 72 | migration.setVersion(MigrationVersion.fromVersion(version)); 73 | migration.setDescription("abc very very very very very very very very very very long"); 74 | migration.setScript("x"); 75 | migration.setType(MigrationType.CQL); 76 | return migration; 77 | } 78 | 79 | /** 80 | * Creates a migrationResolver for testing. 81 | * 82 | * @param resolvedMigrations The resolved migrations. 83 | * @return The migration resolver. 84 | */ 85 | private MigrationResolver createMigrationResolver(final ResolvedMigration... resolvedMigrations) { 86 | return new MigrationResolver() { 87 | public List resolveMigrations() { 88 | return Arrays.asList(resolvedMigrations); 89 | } 90 | }; 91 | } 92 | 93 | /** 94 | * Creates a metadata table for testing. 95 | * 96 | * @return The metadata table. 97 | */ 98 | private SchemaVersionDAO createSchemaVersionDAO() { 99 | SchemaVersionDAO metaDataTable = mock(SchemaVersionDAO.class); 100 | when(metaDataTable.findAppliedMigrations()).thenReturn(new ArrayList()); 101 | return metaDataTable; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/config/MigrationConfigs.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.config; 2 | 3 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 4 | import com.contrastsecurity.cassandra.migration.utils.StringUtils; 5 | 6 | public class MigrationConfigs { 7 | public enum MigrationProperty { 8 | SCRIPTS_ENCODING("cassandra.migration.scripts.encoding", "Encoding for CQL scripts"), 9 | SCRIPTS_LOCATIONS("cassandra.migration.scripts.locations", "Locations of the migration scripts in CSV format"), 10 | ALLOW_OUTOFORDER("cassandra.migration.scripts.allowoutoforder", "Allow out of order migration"), 11 | TARGET_VERSION("cassandra.migration.version.target", "The target version. Migrations with a higher version number will be ignored."); 12 | 13 | private String name; 14 | private String description; 15 | 16 | MigrationProperty(String name, String description) { 17 | this.name = name; 18 | this.description = description; 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public String getDescription() { 26 | return description; 27 | } 28 | } 29 | 30 | public MigrationConfigs() { 31 | String scriptsEncodingP = System.getProperty(MigrationProperty.SCRIPTS_ENCODING.getName()); 32 | if (null != scriptsEncodingP && scriptsEncodingP.trim().length() != 0) 33 | this.encoding = scriptsEncodingP; 34 | 35 | String targetVersionP = System.getProperty(MigrationProperty.TARGET_VERSION.getName()); 36 | if (null != targetVersionP && targetVersionP.trim().length() != 0) 37 | setTargetAsString(targetVersionP); 38 | 39 | String locationsProp = System.getProperty(MigrationProperty.SCRIPTS_LOCATIONS.getName()); 40 | if (locationsProp != null && locationsProp.trim().length() != 0) { 41 | scriptsLocations = StringUtils.tokenizeToStringArray(locationsProp, ","); 42 | } 43 | 44 | String allowOutOfOrderProp = System.getProperty(MigrationProperty.ALLOW_OUTOFORDER.getName()); 45 | if(allowOutOfOrderProp != null && allowOutOfOrderProp.trim().length() != 0) { 46 | setAllowOutOfOrder(allowOutOfOrderProp); 47 | } 48 | } 49 | 50 | /** 51 | * The encoding of Cql migration scripts (default: UTF-8) 52 | */ 53 | private String encoding = "UTF-8"; 54 | 55 | /** 56 | * Locations of the migration scripts in CSV format (default: db/migration) 57 | */ 58 | private String[] scriptsLocations = {"db/migration"}; 59 | 60 | /** 61 | * Allow out of order migrations (default: false) 62 | */ 63 | private boolean allowOutOfOrder = false; 64 | 65 | /** 66 | * The target version. Migrations with a higher version number will be ignored. (default: the latest version) 67 | */ 68 | private MigrationVersion target = MigrationVersion.LATEST; 69 | 70 | public String getEncoding() { 71 | return encoding; 72 | } 73 | 74 | public void setEncoding(String encoding) { 75 | this.encoding = encoding; 76 | } 77 | 78 | public String[] getScriptsLocations() { 79 | return scriptsLocations; 80 | } 81 | 82 | public void setScriptsLocations(String[] scriptsLocations) { 83 | this.scriptsLocations = scriptsLocations; 84 | } 85 | 86 | public boolean isAllowOutOfOrder() { 87 | return allowOutOfOrder; 88 | } 89 | 90 | public void setAllowOutOfOrder(String allowOutOfOrder) { 91 | this.allowOutOfOrder = Boolean.parseBoolean(allowOutOfOrder); 92 | } 93 | 94 | public void setAllowOutOfOrder(boolean allowOutOfOrder) { 95 | this.allowOutOfOrder = allowOutOfOrder; 96 | } 97 | 98 | public MigrationVersion getTarget() { 99 | return target; 100 | } 101 | 102 | /** 103 | * Migrations with a higher version number will be ignored. (default: the latest version) 104 | * @param target Target version 105 | */ 106 | public void setTargetAsString(String target) { 107 | this.target = MigrationVersion.fromVersion(target); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/FileCopyUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import java.io.*; 19 | 20 | /** 21 | * Utility class for copying files and their contents. Inspired by Spring's own. 22 | */ 23 | public class FileCopyUtils { 24 | /** 25 | * Prevent instantiation. 26 | */ 27 | private FileCopyUtils() { 28 | // Do nothing 29 | } 30 | 31 | /** 32 | * Copy the contents of the given Reader into a String. 33 | * Closes the reader when done. 34 | * 35 | * @param in the reader to copy from 36 | * @return the String that has been copied to 37 | * @throws IOException in case of I/O errors 38 | */ 39 | public static String copyToString(Reader in) throws IOException { 40 | StringWriter out = new StringWriter(); 41 | copy(in, out); 42 | String str = out.toString(); 43 | 44 | //Strip UTF-8 BOM if necessary 45 | if (str.startsWith("\ufeff")) { 46 | return str.substring(1); 47 | } 48 | 49 | return str; 50 | } 51 | 52 | /** 53 | * Copy the contents of the given InputStream into a new byte array. 54 | * Closes the stream when done. 55 | * 56 | * @param in the stream to copy from 57 | * @return the new byte array that has been copied to 58 | * @throws IOException in case of I/O errors 59 | */ 60 | public static byte[] copyToByteArray(InputStream in) throws IOException { 61 | ByteArrayOutputStream out = new ByteArrayOutputStream(4096); 62 | copy(in, out); 63 | return out.toByteArray(); 64 | } 65 | 66 | /** 67 | * Copy the contents of the given Reader to the given Writer. 68 | * Closes both when done. 69 | * 70 | * @param in the Reader to copy from 71 | * @param out the Writer to copy to 72 | * @throws IOException in case of I/O errors 73 | */ 74 | private static void copy(Reader in, Writer out) throws IOException { 75 | try { 76 | char[] buffer = new char[4096]; 77 | int bytesRead; 78 | while ((bytesRead = in.read(buffer)) != -1) { 79 | out.write(buffer, 0, bytesRead); 80 | } 81 | out.flush(); 82 | } finally { 83 | try { 84 | in.close(); 85 | } catch (IOException ex) { 86 | //Ignore 87 | } 88 | try { 89 | out.close(); 90 | } catch (IOException ex) { 91 | //Ignore 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Copy the contents of the given InputStream to the given OutputStream. 98 | * Closes both streams when done. 99 | * 100 | * @param in the stream to copy from 101 | * @param out the stream to copy to 102 | * @return the number of bytes copied 103 | * @throws IOException in case of I/O errors 104 | */ 105 | private static int copy(InputStream in, OutputStream out) throws IOException { 106 | try { 107 | int byteCount = 0; 108 | byte[] buffer = new byte[4096]; 109 | int bytesRead; 110 | while ((bytesRead = in.read(buffer)) != -1) { 111 | out.write(buffer, 0, bytesRead); 112 | byteCount += bytesRead; 113 | } 114 | out.flush(); 115 | return byteCount; 116 | } finally { 117 | try { 118 | in.close(); 119 | } catch (IOException ex) { 120 | //Ignore 121 | } 122 | try { 123 | out.close(); 124 | } catch (IOException ex) { 125 | //Ignore 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/FileSystemClassPathLocationScanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import com.contrastsecurity.cassandra.migration.logging.Log; 19 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 20 | import com.contrastsecurity.cassandra.migration.utils.UrlUtils; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.util.Set; 26 | import java.util.TreeSet; 27 | 28 | /** 29 | * ClassPathLocationScanner for the file system. 30 | */ 31 | public class FileSystemClassPathLocationScanner implements ClassPathLocationScanner { 32 | private static final Log LOG = LogFactory.getLog(FileSystemClassPathLocationScanner.class); 33 | 34 | public Set findResourceNames(String location, URL locationUrl) throws IOException { 35 | String filePath = UrlUtils.toFilePath(locationUrl); 36 | File folder = new File(filePath); 37 | if (!folder.isDirectory()) { 38 | LOG.debug("Skipping path as it is not a directory: " + filePath); 39 | return new TreeSet(); 40 | } 41 | 42 | String classPathRootOnDisk = filePath.substring(0, filePath.length() - location.length()); 43 | if (!classPathRootOnDisk.endsWith(File.separator)) { 44 | classPathRootOnDisk = classPathRootOnDisk + File.separator; 45 | } 46 | LOG.debug("Scanning starting at classpath root in filesystem: " + classPathRootOnDisk); 47 | return findResourceNamesFromFileSystem(classPathRootOnDisk, location, folder); 48 | } 49 | 50 | /** 51 | * Finds all the resource names contained in this file system folder. 52 | * 53 | * @param classPathRootOnDisk The location of the classpath root on disk, with a trailing slash. 54 | * @param scanRootLocation The root location of the scan on the classpath, without leading or trailing slashes. 55 | * @param folder The folder to look for resources under on disk. 56 | * @return The resource names; 57 | * @throws IOException when the folder could not be read. 58 | */ 59 | /*private -> for testing*/ 60 | @SuppressWarnings("ConstantConditions") 61 | Set findResourceNamesFromFileSystem(String classPathRootOnDisk, String scanRootLocation, File folder) throws IOException { 62 | LOG.debug("Scanning for resources in path: " + folder.getPath() + " (" + scanRootLocation + ")"); 63 | 64 | Set resourceNames = new TreeSet(); 65 | 66 | File[] files = folder.listFiles(); 67 | for (File file : files) { 68 | if (file.canRead()) { 69 | if (file.isDirectory()) { 70 | resourceNames.addAll(findResourceNamesFromFileSystem(classPathRootOnDisk, scanRootLocation, file)); 71 | } else { 72 | resourceNames.add(toResourceNameOnClasspath(classPathRootOnDisk, file)); 73 | } 74 | } 75 | } 76 | 77 | return resourceNames; 78 | } 79 | 80 | /** 81 | * Converts this file into a resource name on the classpath. 82 | * 83 | * @param classPathRootOnDisk The location of the classpath root on disk, with a trailing slash. 84 | * @param file The file. 85 | * @return The resource name on the classpath. 86 | * @throws IOException when the file could not be read. 87 | */ 88 | private String toResourceNameOnClasspath(String classPathRootOnDisk, File file) throws IOException { 89 | String fileName = file.getAbsolutePath().replace("\\", "/"); 90 | 91 | //Cut off the part on disk leading to the root of the classpath 92 | //This leaves a resource name starting with the scanRootLocation, 93 | // with no leading slash, containing subDirs and the fileName. 94 | return fileName.substring(classPathRootOnDisk.length()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/JarFileClassPathLocationScanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import java.io.IOException; 19 | import java.net.JarURLConnection; 20 | import java.net.URISyntaxException; 21 | import java.net.URL; 22 | import java.net.URLConnection; 23 | import java.util.Enumeration; 24 | import java.util.Set; 25 | import java.util.TreeSet; 26 | import java.util.jar.JarEntry; 27 | import java.util.jar.JarFile; 28 | 29 | /** 30 | * ClassPathLocationScanner for jar files. 31 | */ 32 | public class JarFileClassPathLocationScanner implements ClassPathLocationScanner { 33 | public Set findResourceNames(String location, URL locationUrl) throws IOException { 34 | JarFile jarFile = getJarFromUrl(locationUrl); 35 | 36 | try { 37 | // For Tomcat and non-expanded WARs. 38 | String prefix = jarFile.getName().toLowerCase().endsWith(".war") ? "WEB-INF/classes/" : ""; 39 | return findResourceNamesFromJarFile(jarFile, prefix, location); 40 | } finally { 41 | jarFile.close(); 42 | } 43 | } 44 | 45 | /** 46 | * Retrieves the Jar file represented by this URL. 47 | * 48 | * @param locationUrl The URL of the jar. 49 | * @return The jar file. 50 | * @throws IOException when the jar could not be resolved. 51 | */ 52 | private JarFile getJarFromUrl(URL locationUrl) throws IOException { 53 | URLConnection con = locationUrl.openConnection(); 54 | if (con instanceof JarURLConnection) { 55 | // Should usually be the case for traditional JAR files. 56 | JarURLConnection jarCon = (JarURLConnection) con; 57 | jarCon.setUseCaches(false); 58 | return jarCon.getJarFile(); 59 | } 60 | 61 | // No JarURLConnection -> need to resort to URL file parsing. 62 | // We'll assume URLs of the format "jar:path!/entry", with the protocol 63 | // being arbitrary as long as following the entry format. 64 | // We'll also handle paths with and without leading "file:" prefix. 65 | String urlFile = locationUrl.getFile(); 66 | 67 | int separatorIndex = urlFile.indexOf("!/"); 68 | if (separatorIndex != -1) { 69 | String jarFileUrl = urlFile.substring(0, separatorIndex); 70 | if (jarFileUrl.startsWith("file:")) { 71 | try { 72 | return new JarFile(new URL(jarFileUrl).toURI().getSchemeSpecificPart()); 73 | } catch (URISyntaxException ex) { 74 | // Fallback for URLs that are not valid URIs (should hardly ever happen). 75 | return new JarFile(jarFileUrl.substring("file:".length())); 76 | } 77 | } 78 | return new JarFile(jarFileUrl); 79 | } 80 | 81 | return new JarFile(urlFile); 82 | } 83 | 84 | /** 85 | * Finds all the resource names contained in this directory within this jar file. 86 | * 87 | * @param jarFile The jar file. 88 | * @param prefix The prefix to ignore within the jar file. 89 | * @param location The location to look under. 90 | * @return The resource names. 91 | * @throws IOException when reading the jar file failed. 92 | */ 93 | private Set findResourceNamesFromJarFile(JarFile jarFile, String prefix, String location) throws IOException { 94 | String toScan = prefix + location + (location.endsWith("/") ? "" : "/"); 95 | Set resourceNames = new TreeSet(); 96 | 97 | Enumeration entries = jarFile.entries(); 98 | while (entries.hasMoreElements()) { 99 | String entryName = entries.nextElement().getName(); 100 | if (entryName.startsWith(toScan)) { 101 | resourceNames.add(entryName.substring(prefix.length())); 102 | } 103 | } 104 | 105 | return resourceNames; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/classpath/ClassPathResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.classpath; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.utils.FileCopyUtils; 20 | import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; 21 | 22 | import java.io.*; 23 | import java.net.URL; 24 | import java.net.URLDecoder; 25 | import java.nio.charset.Charset; 26 | 27 | /** 28 | * A resource on the classpath. 29 | */ 30 | public class ClassPathResource implements Comparable, Resource { 31 | /** 32 | * The location of the resource on the classpath. 33 | */ 34 | private String location; 35 | 36 | /** 37 | * The ClassLoader to use. 38 | */ 39 | private ClassLoader classLoader; 40 | 41 | /** 42 | * Creates a new ClassPathResource. 43 | * 44 | * @param location The location of the resource on the classpath. 45 | * @param classLoader The ClassLoader to use. 46 | */ 47 | public ClassPathResource(String location, ClassLoader classLoader) { 48 | this.location = location; 49 | this.classLoader = classLoader; 50 | } 51 | 52 | public String getLocation() { 53 | return location; 54 | } 55 | 56 | public String getLocationOnDisk() { 57 | URL url = getUrl(); 58 | if (url == null) { 59 | throw new CassandraMigrationException("Unable to location resource on disk: " + location); 60 | } 61 | try { 62 | return new File(URLDecoder.decode(url.getPath(), "UTF-8")).getAbsolutePath(); 63 | } catch (UnsupportedEncodingException e) { 64 | throw new CassandraMigrationException("Unknown encoding: UTF-8", e); 65 | } 66 | } 67 | 68 | /** 69 | * @return The url of this resource. 70 | */ 71 | private URL getUrl() { 72 | return classLoader.getResource(location); 73 | } 74 | 75 | public String loadAsString(String encoding) { 76 | try { 77 | InputStream inputStream = classLoader.getResourceAsStream(location); 78 | if (inputStream == null) { 79 | throw new CassandraMigrationException("Unable to obtain inputstream for resource: " + location); 80 | } 81 | Reader reader = new InputStreamReader(inputStream, Charset.forName(encoding)); 82 | 83 | return FileCopyUtils.copyToString(reader); 84 | } catch (IOException e) { 85 | throw new CassandraMigrationException("Unable to load resource: " + location + " (encoding: " + encoding + ")", e); 86 | } 87 | } 88 | 89 | public byte[] loadAsBytes() { 90 | try { 91 | InputStream inputStream = classLoader.getResourceAsStream(location); 92 | if (inputStream == null) { 93 | throw new CassandraMigrationException("Unable to obtain inputstream for resource: " + location); 94 | } 95 | return FileCopyUtils.copyToByteArray(inputStream); 96 | } catch (IOException e) { 97 | throw new CassandraMigrationException("Unable to load resource: " + location, e); 98 | } 99 | } 100 | 101 | public String getFilename() { 102 | return location.substring(location.lastIndexOf("/") + 1); 103 | } 104 | 105 | public boolean exists() { 106 | return getUrl() != null; 107 | } 108 | 109 | @SuppressWarnings({"RedundantIfStatement"}) 110 | @Override 111 | public boolean equals(Object o) { 112 | if (this == o) return true; 113 | if (o == null || getClass() != o.getClass()) return false; 114 | 115 | ClassPathResource that = (ClassPathResource) o; 116 | 117 | if (!location.equals(that.location)) return false; 118 | 119 | return true; 120 | } 121 | 122 | @Override 123 | public int hashCode() { 124 | return location.hashCode(); 125 | } 126 | 127 | @SuppressWarnings("NullableProblems") 128 | public int compareTo(ClassPathResource o) { 129 | return location.compareTo(o.location); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/ClassUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | 20 | import java.io.UnsupportedEncodingException; 21 | import java.net.URLDecoder; 22 | import java.security.ProtectionDomain; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * Utility methods for dealing with classes. 28 | */ 29 | public class ClassUtils { 30 | /** 31 | * Prevents instantiation. 32 | */ 33 | private ClassUtils() { 34 | // Do nothing 35 | } 36 | 37 | /** 38 | * Creates a new instance of this class. 39 | * 40 | * @param className The fully qualified name of the class to instantiate. 41 | * @param classLoader The ClassLoader to use. 42 | * @param The type of the new instance. 43 | * @return The new instance. 44 | * @throws Exception Thrown when the instantiation failed. 45 | */ 46 | @SuppressWarnings({"unchecked"}) 47 | // Must be synchronized for the Maven Parallel Junit runner to work 48 | public static synchronized T instantiate(String className, ClassLoader classLoader) throws Exception { 49 | return (T) Class.forName(className, true, classLoader).newInstance(); 50 | } 51 | 52 | /** 53 | * Instantiate all these classes. 54 | * 55 | * @param classes A fully qualified class names to instantiate. 56 | * @param classLoader The ClassLoader to use. 57 | * @param The common type for all classes. 58 | * @return The list of instances. 59 | */ 60 | public static List instantiateAll(String[] classes, ClassLoader classLoader) { 61 | List clazzes = new ArrayList(); 62 | for (String clazz : classes) { 63 | if (StringUtils.hasLength(clazz)) { 64 | try { 65 | clazzes.add(ClassUtils.instantiate(clazz, classLoader)); 66 | } catch (Exception e) { 67 | throw new CassandraMigrationException("Unable to instantiate class: " + clazz, e); 68 | } 69 | } 70 | } 71 | return clazzes; 72 | } 73 | 74 | /** 75 | * Determine whether the {@link Class} identified by the supplied name is present 76 | * and can be loaded. Will return {@code false} if either the class or 77 | * one of its dependencies is not present or cannot be loaded. 78 | * 79 | * @param className the name of the class to check 80 | * @param classLoader The ClassLoader to use. 81 | * @return whether the specified class is present 82 | */ 83 | public static boolean isPresent(String className, ClassLoader classLoader) { 84 | try { 85 | classLoader.loadClass(className); 86 | return true; 87 | } catch (Throwable ex) { 88 | // Class or one of its dependencies is not present... 89 | return false; 90 | } 91 | } 92 | 93 | /** 94 | * Computes the short name (name without package) of this class. 95 | * 96 | * @param aClass The class to analyse. 97 | * @return The short name. 98 | */ 99 | public static String getShortName(Class aClass) { 100 | String name = aClass.getName(); 101 | return name.substring(name.lastIndexOf(".") + 1); 102 | } 103 | 104 | /** 105 | * Retrieves the physical location on disk of this class. 106 | * 107 | * @param aClass The class to get the location for. 108 | * @return The absolute path of the .class file. 109 | */ 110 | public static String getLocationOnDisk(Class aClass) { 111 | try { 112 | ProtectionDomain protectionDomain = aClass.getProtectionDomain(); 113 | if (protectionDomain == null) { 114 | //Android 115 | return null; 116 | } 117 | String url = protectionDomain.getCodeSource().getLocation().getPath(); 118 | return URLDecoder.decode(url, "UTF-8"); 119 | } catch (UnsupportedEncodingException e) { 120 | //Can never happen. 121 | return null; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/MigrationInfoHelperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 20 | import com.contrastsecurity.cassandra.migration.utils.Pair; 21 | import org.junit.Test; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | 25 | /** 26 | * Test for MigrationInfoHelper. 27 | */ 28 | public class MigrationInfoHelperTest { 29 | /** 30 | * Tests a schema version that lacks a description. 31 | */ 32 | @Test(expected = CassandraMigrationException.class) 33 | public void extractSchemaVersionNoDescription() { 34 | MigrationInfoHelper.extractVersionAndDescription("9_4", "", "__", ""); 35 | } 36 | 37 | @Test 38 | public void extractSchemaVersionDefaults() { 39 | Pair info = MigrationInfoHelper.extractVersionAndDescription("V9_4__EmailAxel.cql", "V", "__", ".cql"); 40 | MigrationVersion version = info.getLeft(); 41 | String description = info.getRight(); 42 | assertEquals("9.4", version.toString()); 43 | assertEquals("EmailAxel", description); 44 | } 45 | 46 | @Test 47 | public void extractSchemaVersionCustomSeparator() { 48 | Pair info = MigrationInfoHelper.extractVersionAndDescription("V9_4-EmailAxel.cql", "V", "-", ".cql"); 49 | MigrationVersion version = info.getLeft(); 50 | String description = info.getRight(); 51 | assertEquals("9.4", version.toString()); 52 | assertEquals("EmailAxel", description); 53 | } 54 | 55 | /** 56 | * Tests a schema version that includes a description. 57 | */ 58 | @Test 59 | public void extractSchemaVersionWithDescription() { 60 | Pair info = MigrationInfoHelper.extractVersionAndDescription("9_4__EmailAxel", "", "__", ""); 61 | MigrationVersion version = info.getLeft(); 62 | String description = info.getRight(); 63 | assertEquals("9.4", version.toString()); 64 | assertEquals("EmailAxel", description); 65 | } 66 | 67 | /** 68 | * Tests a schema version that includes a description with spaces. 69 | */ 70 | @Test 71 | public void extractSchemaVersionWithDescriptionWithSpaces() { 72 | Pair info = MigrationInfoHelper.extractVersionAndDescription("9_4__Big_jump", "", "__", ""); 73 | MigrationVersion version = info.getLeft(); 74 | String description = info.getRight(); 75 | assertEquals("9.4", version.toString()); 76 | assertEquals("Big jump", description); 77 | } 78 | 79 | /** 80 | * Tests a schema version that includes a version with leading zeroes. 81 | */ 82 | @Test 83 | public void extractSchemaVersionWithLeadingZeroes() { 84 | Pair info = MigrationInfoHelper.extractVersionAndDescription("009_4__EmailAxel", "", "__", ""); 85 | MigrationVersion version = info.getLeft(); 86 | String description = info.getRight(); 87 | assertEquals("009.4", version.toString()); 88 | assertEquals("EmailAxel", description); 89 | } 90 | 91 | @Test(expected = CassandraMigrationException.class) 92 | public void extractSchemaVersionWithLeadingUnderscore() { 93 | MigrationInfoHelper.extractVersionAndDescription("_8_0__Description", "", "__", ""); 94 | } 95 | 96 | @Test(expected = CassandraMigrationException.class) 97 | public void extractSchemaVersionWithLeadingUnderscoreAndPrefix() { 98 | MigrationInfoHelper.extractVersionAndDescription("V_8_0__Description.cql", "V", "__", ".cql"); 99 | } 100 | 101 | @Test 102 | public void extractSchemaVersionWithVUnderscorePrefix() { 103 | Pair info = MigrationInfoHelper.extractVersionAndDescription("V_8_0__Description.cql", "V_", "__", ".cql"); 104 | MigrationVersion version = info.getLeft(); 105 | String description = info.getRight(); 106 | assertEquals("8.0", version.toString()); 107 | assertEquals("Description", description); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/cql/CqlMigrationResolver.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.cassandra.migration.resolver.cql; 2 | 3 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 4 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; 5 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 6 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 7 | import com.contrastsecurity.cassandra.migration.resolver.MigrationInfoHelper; 8 | import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; 9 | import com.contrastsecurity.cassandra.migration.resolver.ResolvedMigrationComparator; 10 | import com.contrastsecurity.cassandra.migration.utils.Pair; 11 | import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; 12 | import com.contrastsecurity.cassandra.migration.utils.scanner.Scanner; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.zip.CRC32; 18 | 19 | /** 20 | * Migration resolver for cql files on the classpath. The cql files must have names like 21 | * V1__Description.cql or V1_1__Description.cql. 22 | */ 23 | public class CqlMigrationResolver implements MigrationResolver { 24 | 25 | /** 26 | * The scanner to use. 27 | */ 28 | private final Scanner scanner; 29 | 30 | /** 31 | * The base directory on the classpath where to migrations are located. 32 | */ 33 | private final ScriptsLocation location; 34 | 35 | /** 36 | * The encoding of Cql migrations. 37 | */ 38 | private final String encoding; 39 | 40 | /** 41 | * The prefix for cql migrations 42 | */ 43 | private final static String CQL_MIGRATION_PREFIX = "V"; 44 | ; 45 | 46 | /** 47 | * The separator for cql migrations 48 | */ 49 | private final static String CQL_MIGRATION_SEPARATOR = "__"; 50 | 51 | /** 52 | * The suffix for cql migrations 53 | */ 54 | private final static String CQL_MIGRATION_SUFFIX = ".cql"; 55 | 56 | /** 57 | * Creates a new instance. 58 | * 59 | * @param classLoader The ClassLoader for loading migrations on the classpath. 60 | * @param location The location on the classpath where to migrations are located. 61 | * @param encoding The encoding of the .cql file. 62 | */ 63 | public CqlMigrationResolver(ClassLoader classLoader, ScriptsLocation location, String encoding) { 64 | this.scanner = new Scanner(classLoader); 65 | this.location = location; 66 | this.encoding = encoding; 67 | } 68 | 69 | public List resolveMigrations() { 70 | List migrations = new ArrayList<>(); 71 | 72 | Resource[] resources = scanner.scanForResources(location, CQL_MIGRATION_PREFIX, CQL_MIGRATION_SUFFIX); 73 | for (Resource resource : resources) { 74 | ResolvedMigration resolvedMigration = extractMigrationInfo(resource); 75 | resolvedMigration.setPhysicalLocation(resource.getLocationOnDisk()); 76 | resolvedMigration.setExecutor(new CqlMigrationExecutor(resource, encoding)); 77 | 78 | migrations.add(resolvedMigration); 79 | } 80 | 81 | Collections.sort(migrations, new ResolvedMigrationComparator()); 82 | return migrations; 83 | } 84 | 85 | /** 86 | * Extracts the migration info for this resource. 87 | * 88 | * @param resource The resource to analyse. 89 | * @return The migration info. 90 | */ 91 | private ResolvedMigration extractMigrationInfo(Resource resource) { 92 | ResolvedMigration migration = new ResolvedMigration(); 93 | 94 | Pair info = 95 | MigrationInfoHelper.extractVersionAndDescription(resource.getFilename(), 96 | CQL_MIGRATION_PREFIX, CQL_MIGRATION_SEPARATOR, CQL_MIGRATION_SUFFIX); 97 | migration.setVersion(info.getLeft()); 98 | migration.setDescription(info.getRight()); 99 | 100 | migration.setScript(extractScriptName(resource)); 101 | 102 | migration.setChecksum(calculateChecksum(resource.loadAsBytes())); 103 | migration.setType(MigrationType.CQL); 104 | return migration; 105 | } 106 | 107 | /** 108 | * Extracts the script name from this resource. 109 | * 110 | * @param resource The resource to process. 111 | * @return The script name. 112 | */ 113 | /* private -> for testing */ String extractScriptName(Resource resource) { 114 | if (location.getPath().isEmpty()) { 115 | return resource.getLocation(); 116 | } 117 | 118 | return resource.getLocation().substring(location.getPath().length() + 1); 119 | } 120 | 121 | /** 122 | * Calculates the checksum of these bytes. 123 | * 124 | * @param bytes The bytes to calculate the checksum for. 125 | * @return The crc-32 checksum of the bytes. 126 | */ 127 | private static int calculateChecksum(byte[] bytes) { 128 | final CRC32 crc32 = new CRC32(); 129 | crc32.update(bytes); 130 | return (int) crc32.getValue(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/info/ResolvedMigration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 19 | import com.contrastsecurity.cassandra.migration.resolver.MigrationExecutor; 20 | 21 | /** 22 | * A migration available on the classpath. 23 | */ 24 | public class ResolvedMigration { 25 | /** 26 | * The target version of this migration. 27 | */ 28 | private MigrationVersion version; 29 | 30 | /** 31 | * The description of the migration. 32 | */ 33 | private String description; 34 | 35 | /** 36 | * The name of the script to execute for this migration, relative to its classpath location. 37 | */ 38 | private String script; 39 | 40 | /** 41 | * The checksum of the migration. 42 | */ 43 | private Integer checksum; 44 | 45 | /** 46 | * The type of migration (CQL, JAVA_DRIVER) 47 | */ 48 | private MigrationType type; 49 | 50 | /** 51 | * The physical location of the migration on disk. 52 | */ 53 | private String physicalLocation; 54 | 55 | /** 56 | * The executor to run this migration. 57 | */ 58 | private MigrationExecutor executor; 59 | 60 | public MigrationVersion getVersion() { 61 | return version; 62 | } 63 | 64 | /** 65 | * @param version The target version of this migration. 66 | */ 67 | public void setVersion(MigrationVersion version) { 68 | this.version = version; 69 | } 70 | 71 | public String getDescription() { 72 | return description; 73 | } 74 | 75 | /** 76 | * @param description The description of the migration. 77 | */ 78 | public void setDescription(String description) { 79 | this.description = description; 80 | } 81 | 82 | public String getScript() { 83 | return script; 84 | } 85 | 86 | /** 87 | * @param script The name of the script to execute for this migration, relative to its classpath location. 88 | */ 89 | public void setScript(String script) { 90 | this.script = script; 91 | } 92 | 93 | public Integer getChecksum() { 94 | return checksum; 95 | } 96 | 97 | /** 98 | * @param checksum The checksum of the migration. 99 | */ 100 | public void setChecksum(Integer checksum) { 101 | this.checksum = checksum; 102 | } 103 | 104 | public MigrationType getType() { 105 | return type; 106 | } 107 | 108 | /** 109 | * @param type The type of migration (INIT, CQL, ...) 110 | */ 111 | public void setType(MigrationType type) { 112 | this.type = type; 113 | } 114 | 115 | 116 | public String getPhysicalLocation() { 117 | return physicalLocation; 118 | } 119 | 120 | /** 121 | * @param physicalLocation The physical location of the migration on disk. 122 | */ 123 | public void setPhysicalLocation(String physicalLocation) { 124 | this.physicalLocation = physicalLocation; 125 | } 126 | 127 | public MigrationExecutor getExecutor() { 128 | return executor; 129 | } 130 | 131 | /** 132 | * @param executor The executor to run this migration. 133 | */ 134 | public void setExecutor(MigrationExecutor executor) { 135 | this.executor = executor; 136 | } 137 | 138 | @SuppressWarnings("NullableProblems") 139 | public int compareTo(ResolvedMigration o) { 140 | return version.compareTo(o.version); 141 | } 142 | 143 | @SuppressWarnings("SimplifiableIfStatement") 144 | @Override 145 | public boolean equals(Object o) { 146 | if (this == o) return true; 147 | if (o == null || getClass() != o.getClass()) return false; 148 | 149 | ResolvedMigration migration = (ResolvedMigration) o; 150 | 151 | if (checksum != null ? !checksum.equals(migration.checksum) : migration.checksum != null) return false; 152 | if (description != null ? !description.equals(migration.description) : migration.description != null) 153 | return false; 154 | if (physicalLocation != null ? !physicalLocation.equals(migration.physicalLocation) : migration.physicalLocation != null) 155 | return false; 156 | if (script != null ? !script.equals(migration.script) : migration.script != null) return false; 157 | if (type != migration.type) return false; 158 | return version.equals(migration.version); 159 | } 160 | 161 | @Override 162 | public int hashCode() { 163 | int result = version.hashCode(); 164 | result = 31 * result + (description != null ? description.hashCode() : 0); 165 | result = 31 * result + (script != null ? script.hashCode() : 0); 166 | result = 31 * result + (checksum != null ? checksum.hashCode() : 0); 167 | result = 31 * result + type.hashCode(); 168 | result = 31 * result + (physicalLocation != null ? physicalLocation.hashCode() : 0); 169 | return result; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/utils/scanner/filesystem/FileSystemScanner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.utils.scanner.filesystem; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.logging.Log; 20 | import com.contrastsecurity.cassandra.migration.logging.LogFactory; 21 | import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.util.Set; 26 | import java.util.TreeSet; 27 | 28 | /** 29 | * FileSystem scanner. 30 | */ 31 | public class FileSystemScanner { 32 | private static final Log LOG = LogFactory.getLog(FileSystemScanner.class); 33 | 34 | /** 35 | * Scans the FileSystem for resources under the specified location, starting with the specified prefix and ending with 36 | * the specified suffix. 37 | * 38 | * @param path The path in the filesystem to start searching. Subdirectories are also searched. 39 | * @param prefix The prefix of the resource names to match. 40 | * @param suffix The suffix of the resource names to match. 41 | * @return The resources that were found. 42 | * @throws IOException when the location could not be scanned. 43 | */ 44 | public Resource[] scanForResources(String path, String prefix, String suffix) throws IOException { 45 | LOG.debug("Scanning for filesystem resources at '" + path + "' (Prefix: '" + prefix + "', Suffix: '" + suffix + "')"); 46 | 47 | if (!new File(path).isDirectory()) { 48 | throw new CassandraMigrationException("Invalid filesystem path: " + path); 49 | } 50 | 51 | Set resources = new TreeSet(); 52 | 53 | Set resourceNames = findResourceNames(path, prefix, suffix); 54 | for (String resourceName : resourceNames) { 55 | resources.add(new FileSystemResource(resourceName)); 56 | LOG.debug("Found filesystem resource: " + resourceName); 57 | } 58 | 59 | return resources.toArray(new Resource[resources.size()]); 60 | } 61 | 62 | /** 63 | * Finds the resources names present at this location and below on the classpath starting with this prefix and 64 | * ending with this suffix. 65 | * 66 | * @param path The path on the classpath to scan. 67 | * @param prefix The filename prefix to match. 68 | * @param suffix The filename suffix to match. 69 | * @return The resource names. 70 | * @throws IOException when scanning this location failed. 71 | */ 72 | private Set findResourceNames(String path, String prefix, String suffix) throws IOException { 73 | Set resourceNames = findResourceNamesFromFileSystem(path, new File(path)); 74 | return filterResourceNames(resourceNames, prefix, suffix); 75 | } 76 | 77 | /** 78 | * Finds all the resource names contained in this file system folder. 79 | * 80 | * @param scanRootLocation The root location of the scan on disk. 81 | * @param folder The folder to look for resources under on disk. 82 | * @return The resource names; 83 | * @throws IOException when the folder could not be read. 84 | */ 85 | @SuppressWarnings("ConstantConditions") 86 | private Set findResourceNamesFromFileSystem(String scanRootLocation, File folder) throws IOException { 87 | LOG.debug("Scanning for resources in path: " + folder.getPath() + " (" + scanRootLocation + ")"); 88 | 89 | Set resourceNames = new TreeSet(); 90 | 91 | File[] files = folder.listFiles(); 92 | for (File file : files) { 93 | if (file.canRead()) { 94 | if (file.isDirectory()) { 95 | resourceNames.addAll(findResourceNamesFromFileSystem(scanRootLocation, file)); 96 | } else { 97 | resourceNames.add(file.getPath()); 98 | } 99 | } 100 | } 101 | 102 | return resourceNames; 103 | } 104 | 105 | /** 106 | * Filters this list of resource names to only include the ones whose filename matches this prefix and this suffix. 107 | * 108 | * @param resourceNames The names to filter. 109 | * @param prefix The prefix to match. 110 | * @param suffix The suffix to match. 111 | * @return The filtered names set. 112 | */ 113 | private Set filterResourceNames(Set resourceNames, String prefix, String suffix) { 114 | Set filteredResourceNames = new TreeSet(); 115 | for (String resourceName : resourceNames) { 116 | String fileName = resourceName.substring(resourceName.lastIndexOf(File.separator) + 1); 117 | if (fileName.startsWith(prefix) && fileName.endsWith(suffix) 118 | && (fileName.length() > (prefix + suffix).length())) { 119 | filteredResourceNames.add(resourceName); 120 | } else { 121 | LOG.debug("Filtering out resource: " + resourceName + " (filename: " + fileName + ")"); 122 | } 123 | } 124 | return filteredResourceNames; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/info/MigrationVersion.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.info; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | 20 | import java.math.BigInteger; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.regex.Pattern; 24 | 25 | public class MigrationVersion implements Comparable { 26 | 27 | public static final MigrationVersion EMPTY = new MigrationVersion(null, "<< Empty Schema >>"); 28 | public static final MigrationVersion LATEST = new MigrationVersion(BigInteger.valueOf(-1), "<< Latest Version >>"); 29 | public static final MigrationVersion CURRENT = new MigrationVersion(BigInteger.valueOf(-2), "<< Current Version >>"); 30 | 31 | private static final String TABLE = "cassandra_migration_version"; 32 | private List versionParts; 33 | private String displayText; 34 | 35 | private static Pattern splitPattern = Pattern.compile("\\.(?=\\d)"); 36 | 37 | public MigrationVersion(BigInteger version, String displayText) { 38 | List tmp = new ArrayList<>(); 39 | tmp.add(version); 40 | this.displayText = displayText; 41 | init(tmp, displayText); 42 | } 43 | 44 | private MigrationVersion(String version) { 45 | String normalizedVersion = version.replace('_', '.'); 46 | init(tokenize(normalizedVersion), normalizedVersion); 47 | } 48 | 49 | private void init(List versionParts, String displayText) { 50 | this.versionParts = versionParts; 51 | this.displayText = displayText; 52 | } 53 | 54 | public static MigrationVersion fromVersion(String version) { 55 | if ("current".equalsIgnoreCase(version)) return CURRENT; 56 | if (LATEST.getVersion().equals(version)) return LATEST; 57 | if (version == null) return EMPTY; 58 | return new MigrationVersion(version); 59 | } 60 | 61 | public String getVersion() { 62 | if (this.equals(EMPTY)) return null; 63 | if (this.equals(LATEST)) return Long.toString(Long.MAX_VALUE); 64 | return displayText; 65 | } 66 | 67 | /** 68 | * @return The textual representation of the version. 69 | */ 70 | @Override 71 | public String toString() { 72 | return displayText; 73 | } 74 | 75 | @Override 76 | public boolean equals(Object o) { 77 | if (this == o) return true; 78 | if (o == null || getClass() != o.getClass()) return false; 79 | 80 | MigrationVersion version1 = (MigrationVersion) o; 81 | 82 | return compareTo(version1) == 0; 83 | } 84 | 85 | @Override 86 | public int hashCode() { 87 | return versionParts == null ? 0 : versionParts.hashCode(); 88 | } 89 | 90 | @Override 91 | public int compareTo(MigrationVersion o) { 92 | if (o == null) { 93 | return 1; 94 | } 95 | 96 | if (this == EMPTY) { 97 | return o == EMPTY ? 0 : Integer.MIN_VALUE; 98 | } 99 | 100 | if (this == CURRENT) { 101 | return o == CURRENT ? 0 : Integer.MIN_VALUE; 102 | } 103 | 104 | if (this == LATEST) { 105 | return o == LATEST ? 0 : Integer.MAX_VALUE; 106 | } 107 | 108 | if (o == EMPTY) { 109 | return Integer.MAX_VALUE; 110 | } 111 | 112 | if (o == CURRENT) { 113 | return Integer.MAX_VALUE; 114 | } 115 | 116 | if (o == LATEST) { 117 | return Integer.MIN_VALUE; 118 | } 119 | final List elements1 = versionParts; 120 | final List elements2 = o.versionParts; 121 | int largestNumberOfElements = Math.max(elements1.size(), elements2.size()); 122 | for (int i = 0; i < largestNumberOfElements; i++) { 123 | final int compared = getOrZero(elements1, i).compareTo(getOrZero(elements2, i)); 124 | if (compared != 0) { 125 | return compared; 126 | } 127 | } 128 | return 0; 129 | } 130 | 131 | private BigInteger getOrZero(List elements, int i) { 132 | return i < elements.size() ? elements.get(i) : BigInteger.ZERO; 133 | } 134 | 135 | public String getTable() { 136 | return TABLE; 137 | } 138 | 139 | private void setVersion(String version) { 140 | String normalizedVersion = version.replace('_', '.'); 141 | this.versionParts = tokenize(normalizedVersion); 142 | this.displayText = normalizedVersion; 143 | } 144 | 145 | private List tokenize(String str) { 146 | List numbers = new ArrayList<>(); 147 | for (String number : splitPattern.split(str)) { 148 | try { 149 | numbers.add(new BigInteger(number)); 150 | } catch (NumberFormatException e) { 151 | throw new CassandraMigrationException( 152 | "Invalid version containing non-numeric characters. Only 0..9 and . are allowed. Invalid version: " 153 | + str); 154 | } 155 | } 156 | for (int i = numbers.size() - 1; i > 0; i--) { 157 | if (!numbers.get(i).equals(BigInteger.ZERO)) break; 158 | numbers.remove(i); 159 | } 160 | return numbers; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/cassandra/migration/resolver/java/JavaMigrationResolver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver.java; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.api.MigrationChecksumProvider; 20 | import com.contrastsecurity.cassandra.migration.api.MigrationInfoProvider; 21 | import com.contrastsecurity.cassandra.migration.api.JavaMigration; 22 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 23 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; 24 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 25 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 26 | import com.contrastsecurity.cassandra.migration.resolver.MigrationInfoHelper; 27 | import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; 28 | import com.contrastsecurity.cassandra.migration.resolver.ResolvedMigrationComparator; 29 | import com.contrastsecurity.cassandra.migration.utils.ClassUtils; 30 | import com.contrastsecurity.cassandra.migration.utils.Pair; 31 | import com.contrastsecurity.cassandra.migration.utils.StringUtils; 32 | import com.contrastsecurity.cassandra.migration.utils.scanner.Scanner; 33 | 34 | import java.util.ArrayList; 35 | import java.util.Collections; 36 | import java.util.List; 37 | 38 | /** 39 | * Migration resolver for Java migrations. The classes must have a name like V1 or V1_1_3 or V1__Description 40 | * or V1_1_3__Description. 41 | */ 42 | public class JavaMigrationResolver implements MigrationResolver { 43 | /** 44 | * The base package on the classpath where to migrations are located. 45 | */ 46 | private final ScriptsLocation location; 47 | 48 | /** 49 | * The ClassLoader to use. 50 | */ 51 | private ClassLoader classLoader; 52 | 53 | /** 54 | * Creates a new instance. 55 | * 56 | * @param location The base package on the classpath where to migrations are located. 57 | * @param classLoader The ClassLoader for loading migrations on the classpath. 58 | */ 59 | public JavaMigrationResolver(ClassLoader classLoader, ScriptsLocation location) { 60 | this.location = location; 61 | this.classLoader = classLoader; 62 | } 63 | 64 | public List resolveMigrations() { 65 | List migrations = new ArrayList(); 66 | 67 | if (!location.isClassPath()) { 68 | return migrations; 69 | } 70 | 71 | try { 72 | Class[] classes = new Scanner(classLoader).scanForClasses(location, JavaMigration.class); 73 | for (Class clazz : classes) { 74 | JavaMigration javaMigration = ClassUtils.instantiate(clazz.getName(), classLoader); 75 | 76 | ResolvedMigration migrationInfo = extractMigrationInfo(javaMigration); 77 | migrationInfo.setPhysicalLocation(ClassUtils.getLocationOnDisk(clazz)); 78 | migrationInfo.setExecutor(new JavaMigrationExecutor(javaMigration)); 79 | 80 | migrations.add(migrationInfo); 81 | } 82 | } catch (Exception e) { 83 | throw new CassandraMigrationException("Unable to resolve Java migrations in location: " + location, e); 84 | } 85 | 86 | Collections.sort(migrations, new ResolvedMigrationComparator()); 87 | return migrations; 88 | } 89 | 90 | /** 91 | * Extracts the migration info from this migration. 92 | * 93 | * @param javaMigration The migration to analyse. 94 | * @return The migration info. 95 | */ 96 | ResolvedMigration extractMigrationInfo(JavaMigration javaMigration) { 97 | Integer checksum = null; 98 | if (javaMigration instanceof MigrationChecksumProvider) { 99 | MigrationChecksumProvider checksumProvider = (MigrationChecksumProvider) javaMigration; 100 | checksum = checksumProvider.getChecksum(); 101 | } 102 | 103 | MigrationVersion version; 104 | String description; 105 | if (javaMigration instanceof MigrationInfoProvider) { 106 | MigrationInfoProvider infoProvider = (MigrationInfoProvider) javaMigration; 107 | version = infoProvider.getVersion(); 108 | description = infoProvider.getDescription(); 109 | if (!StringUtils.hasText(description)) { 110 | throw new CassandraMigrationException("Missing description for migration " + version); 111 | } 112 | } else { 113 | Pair info = 114 | MigrationInfoHelper.extractVersionAndDescription( 115 | ClassUtils.getShortName(javaMigration.getClass()), "V", "__", ""); 116 | version = info.getLeft(); 117 | description = info.getRight(); 118 | } 119 | 120 | String script = javaMigration.getClass().getName(); 121 | 122 | 123 | ResolvedMigration resolvedMigration = new ResolvedMigration(); 124 | resolvedMigration.setVersion(version); 125 | resolvedMigration.setDescription(description); 126 | resolvedMigration.setScript(script); 127 | resolvedMigration.setChecksum(checksum); 128 | resolvedMigration.setType(MigrationType.JAVA_DRIVER); 129 | return resolvedMigration; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/cassandra/migration/resolver/CompositeMigrationResolverTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010-2015 Axel Fontaine 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.contrastsecurity.cassandra.migration.resolver; 17 | 18 | import com.contrastsecurity.cassandra.migration.CassandraMigrationException; 19 | import com.contrastsecurity.cassandra.migration.config.MigrationType; 20 | import com.contrastsecurity.cassandra.migration.config.ScriptsLocations; 21 | import com.contrastsecurity.cassandra.migration.info.MigrationVersion; 22 | import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; 23 | import org.junit.Test; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.List; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | /** 33 | * Test for CompositeMigrationResolver. 34 | */ 35 | public class CompositeMigrationResolverTest { 36 | @Test 37 | public void resolveMigrationsMultipleLocations() { 38 | MigrationResolver migrationResolver = new CompositeMigrationResolver( 39 | Thread.currentThread().getContextClassLoader(), 40 | new ScriptsLocations("migration/subdir/dir2", "migration.outoforder", "migration/subdir/dir1"), 41 | "UTF-8"); 42 | 43 | Collection migrations = migrationResolver.resolveMigrations(); 44 | List migrationList = new ArrayList(migrations); 45 | 46 | assertEquals(3, migrations.size()); 47 | assertEquals("First", migrationList.get(0).getDescription()); 48 | assertEquals("Late arrival", migrationList.get(1).getDescription()); 49 | assertEquals("Add contents table", migrationList.get(2).getDescription()); 50 | } 51 | 52 | /** 53 | * Checks that migrations are properly collected, eliminating all exact duplicates. 54 | */ 55 | @Test 56 | public void collectMigrations() { 57 | MigrationResolver migrationResolver = new MigrationResolver() { 58 | public List resolveMigrations() { 59 | List migrations = new ArrayList(); 60 | 61 | migrations.add(createTestMigration(MigrationType.JAVA_DRIVER, "1", "Description", "Migration1", 123)); 62 | migrations.add(createTestMigration(MigrationType.JAVA_DRIVER, "1", "Description", "Migration1", 123)); 63 | migrations.add(createTestMigration(MigrationType.CQL, "2", "Description2", "Migration2", 1234)); 64 | return migrations; 65 | } 66 | }; 67 | Collection migrationResolvers = new ArrayList(); 68 | migrationResolvers.add(migrationResolver); 69 | 70 | Collection migrations = CompositeMigrationResolver.collectMigrations(migrationResolvers); 71 | assertEquals(2, migrations.size()); 72 | } 73 | 74 | @Test 75 | public void checkForIncompatibilitiesMessage() { 76 | ResolvedMigration migration1 = createTestMigration(MigrationType.CQL, "1", "First", "V1__First.cql", 123); 77 | migration1.setPhysicalLocation("target/test-classes/migration/validate/V1__First.cql"); 78 | 79 | ResolvedMigration migration2 = createTestMigration(MigrationType.JAVA_DRIVER, "1", "Description", "Migration1", 123); 80 | migration2.setPhysicalLocation("Migration1"); 81 | 82 | List migrations = new ArrayList(); 83 | migrations.add(migration1); 84 | migrations.add(migration2); 85 | 86 | try { 87 | CompositeMigrationResolver.checkForIncompatibilities(migrations); 88 | } catch (CassandraMigrationException e) { 89 | assertTrue(e.getMessage().contains("target/test-classes/migration/validate/V1__First.cql")); 90 | assertTrue(e.getMessage().contains("Migration1")); 91 | } 92 | } 93 | 94 | /** 95 | * Makes sure no validation exception is thrown. 96 | */ 97 | @Test 98 | public void checkForIncompatibilitiesNoConflict() { 99 | List migrations = new ArrayList(); 100 | migrations.add(createTestMigration(MigrationType.JAVA_DRIVER, "1", "Description", "Migration1", 123)); 101 | migrations.add(createTestMigration(MigrationType.CQL, "2", "Description2", "Migration2", 1234)); 102 | 103 | CompositeMigrationResolver.checkForIncompatibilities(migrations); 104 | } 105 | 106 | /** 107 | * Creates a migration for our tests. 108 | * 109 | * @param aMigrationType The migration type. 110 | * @param aVersion The version. 111 | * @param aDescription The description. 112 | * @param aScript The script. 113 | * @param aChecksum The checksum. 114 | * @return The new test migration. 115 | */ 116 | private ResolvedMigration createTestMigration(final MigrationType aMigrationType, final String aVersion, final String aDescription, final String aScript, final Integer aChecksum) { 117 | ResolvedMigration migration = new ResolvedMigration(); 118 | migration.setVersion(MigrationVersion.fromVersion(aVersion)); 119 | migration.setDescription(aDescription); 120 | migration.setScript(aScript); 121 | migration.setChecksum(aChecksum); 122 | migration.setType(aMigrationType); 123 | return migration; 124 | } 125 | 126 | } 127 | --------------------------------------------------------------------------------