package com.contrastsecurity.cassandra.migration.resolver.cql; import com.contrastsecurity.cassandra.migration.config.MigrationType; import com.contrastsecurity.cassandra.migration.config.ScriptsLocation; import com.contrastsecurity.cassandra.migration.info.MigrationVersion; import com.contrastsecurity.cassandra.migration.info.ResolvedMigration; import com.contrastsecurity.cassandra.migration.resolver.MigrationInfoHelper; import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver; import com.contrastsecurity.cassandra.migration.resolver.ResolvedMigrationComparator; import com.contrastsecurity.cassandra.migration.utils.Pair; import com.contrastsecurity.cassandra.migration.utils.scanner.Resource; import com.contrastsecurity.cassandra.migration.utils.scanner.Scanner; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.zip.CRC32; /** * Migration resolver for cql files on the classpath. The cql files must have names like * V1__Description.cql or V1_1__Description.cql. */ public class CqlMigrationResolver implements MigrationResolver { /** * The scanner to use. */ private final Scanner scanner; /** * The base directory on the classpath where to migrations are located. */ private final ScriptsLocation location; /** * The encoding of Cql migrations. */ private final String encoding; /** * The prefix for cql migrations */ private final static String CQL_MIGRATION_PREFIX = "V"; ; /** * The separator for cql migrations */ private final static String CQL_MIGRATION_SEPARATOR = "__"; /** * The suffix for cql migrations */ private final static String CQL_MIGRATION_SUFFIX = ".cql"; /** * Creates a new instance. * * @param classLoader The ClassLoader for loading migrations on the classpath. * @param location The location on the classpath where to migrations are located. * @param encoding The encoding of the .cql file. */ public CqlMigrationResolver(ClassLoader classLoader, ScriptsLocation location, String encoding) { this.scanner = new Scanner(classLoader); this.location = location; this.encoding = encoding; } public List<ResolvedMigration> resolveMigrations() { List<ResolvedMigration> migrations = new ArrayList<>(); Resource[] resources = scanner.scanForResources(location, CQL_MIGRATION_PREFIX, CQL_MIGRATION_SUFFIX); for (Resource resource : resources) { ResolvedMigration resolvedMigration = extractMigrationInfo(resource); resolvedMigration.setPhysicalLocation(resource.getLocationOnDisk()); resolvedMigration.setExecutor(new CqlMigrationExecutor(resource, encoding)); migrations.add(resolvedMigration); } Collections.sort(migrations, new ResolvedMigrationComparator()); return migrations; } /** * Extracts the migration info for this resource. * * @param resource The resource to analyse. * @return The migration info. */ private ResolvedMigration extractMigrationInfo(Resource resource) { ResolvedMigration migration = new ResolvedMigration(); Pair<MigrationVersion, String> info = MigrationInfoHelper.extractVersionAndDescription(resource.getFilename(), CQL_MIGRATION_PREFIX, CQL_MIGRATION_SEPARATOR, CQL_MIGRATION_SUFFIX); migration.setVersion(info.getLeft()); migration.setDescription(info.getRight()); migration.setScript(extractScriptName(resource)); migration.setChecksum(calculateChecksum(resource.loadAsBytes())); migration.setType(MigrationType.CQL); return migration; } /** * Extracts the script name from this resource. * * @param resource The resource to process. * @return The script name. */ /* private -> for testing */ String extractScriptName(Resource resource) { if (location.getPath().isEmpty()) { return resource.getLocation(); } return resource.getLocation().substring(location.getPath().length() + 1); } /** * Calculates the checksum of these bytes. * * @param bytes The bytes to calculate the checksum for. * @return The crc-32 checksum of the bytes. */ private static int calculateChecksum(byte[] bytes) { final CRC32 crc32 = new CRC32(); crc32.update(bytes); return (int) crc32.getValue(); } }