liquibase.integration.jakarta.cdi.SchemesCDIConfigBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liquibase-cdi-jakarta Show documentation
Show all versions of liquibase-cdi-jakarta Show documentation
Configures Liquibase for use in a CDI 3.0+ environment
package liquibase.integration.jakarta.cdi;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import liquibase.Scope;
import liquibase.integration.jakarta.cdi.annotations.Liquibase;
import liquibase.integration.jakarta.cdi.annotations.LiquibaseSchema;
import liquibase.logging.Logger;
import liquibase.resource.DirectoryResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.util.FileUtil;
import liquibase.util.StreamUtil;
/**
* @author Nikita Lipatov (https://github.com/islonik)
* @since 27/5/17.
*/
@Singleton
public class SchemesCDIConfigBuilder {
private static final Logger log = Scope.getCurrentScope().getLog(SchemesCDIConfigBuilder.class);
private static final String ROOT_PATH = System.getProperty("java.io.tmpdir");
private static final String SCHEMA_NAME = "/schema.template.xml";
private static final String TEMPLATE_NAME = "liquibase.cdi.schema.xml";
private static final String INCLUDE_TPL = "\t%n";
private static final Long FILE_LOCK_TIMEOUT = 50L;
private final BeanManager bm;
private final SchemesTreeBuilder treeBuilder;
@Inject
public SchemesCDIConfigBuilder(BeanManager bm, SchemesTreeBuilder treeBuilder) {
this.bm = bm;
this.treeBuilder = treeBuilder;
}
/**
* API method.
*/
public ResourceAccessor createResourceAccessor() throws IOException {
return new DirectoryResourceAccessor(new File(ROOT_PATH));
}
/**
* API method.
*/
public CDILiquibaseConfig createCDILiquibaseConfig() {
final String id = UUID.randomUUID().toString();
log.fine(String.format("[id = %s] createConfig(). Date: '%s'", id, new Date()));
final InputStream is = SchemesCDIConfigBuilder.class.getResourceAsStream(SCHEMA_NAME);
try {
return jvmLocked(id, new Callable() {
public CDILiquibaseConfig call() throws Exception {
return createCDILiquibaseConfig(id, is);
}
});
} catch (Exception ex) {
log.warning(String.format("[id = %s] Unable to initialize liquibase where '%s'.", id, ex.getMessage()), ex);
return null;
} finally {
try {
is.close();
} catch (IOException ioe) {
log.warning(String.format("[id = %s] IOException during closing an input stream '%s'.", id, ioe.getMessage()), ioe);
}
}
}
private CDILiquibaseConfig createCDILiquibaseConfig(final String id, final InputStream is) throws IOException {
File liquibaseDir = new File(String.format("%s/liquibase/schemes", ROOT_PATH));
if (!liquibaseDir.exists() && (!liquibaseDir.mkdirs())) {
throw new RuntimeException(String.format("[id = %s] Cannot create [%s] dirs.", id, liquibaseDir));
}
log.fine(String.format("[id = %s] Includes directory: [path='%s']", id, liquibaseDir.getAbsolutePath()));
String path = String.format("%s/%s", ROOT_PATH, TEMPLATE_NAME);
File output = new File(path);
if (output.exists()) {
log.fine(String.format("[id = %s] File [path='%s'] already exists, deleting", id, path));
if (output.delete()) {
log.fine(String.format("[id = %s] File [path='%s'] already exists, deleted successfully.", id, path));
} else {
log.fine(String.format("[id = %s] File [path='%s'] already exists, failed to delete.", id, path));
}
}
if (!output.createNewFile()) {
throw new RuntimeException(String.format("[id = %s] Cannot create [%s] file.", id, output));
}
log.info(String.format("[id = %s] File %s was created.", id, output));
log.fine(String.format("[id = %s] Root liquibase file [path='%s'] ready.", id, path));
long start = System.currentTimeMillis();
log.info(String.format("[id = %s] Scanning application for liquibase schemes.", id));
Set> beans = bm.getBeans(Object.class, new AnnotationLiteralDefault());
Set> classesSet = new LinkedHashSet<>();
for (Bean> bean : beans) {
classesSet.add(bean.getBeanClass());
}
Set annotationsSet = new LinkedHashSet<>();
for (Class> clazz : classesSet) {
annotationsSet.add(clazz.getAnnotation(LiquibaseSchema.class));
}
List liquibaseSchemaList = new ArrayList<>();
for (Annotation ann : annotationsSet) {
liquibaseSchemaList.add((LiquibaseSchema) ann);
}
List treeList = treeBuilder.build(id, liquibaseSchemaList);
List resourceList = new ArrayList<>();
for (LiquibaseSchema liquibaseSchema : treeList) {
resourceList.add(liquibaseSchema.resource());
}
List schemaPaths = new ArrayList<>();
for (String[] resources : resourceList) {
for (String resource : resources) {
schemaPaths.add(copyToFile(id, liquibaseDir.getAbsolutePath(), resource));
}
}
StringBuilder schemes = new StringBuilder();
for (String schema : schemaPaths) {
schemes.append(String.format(INCLUDE_TPL, schema)).append("\n");
}
long end = System.currentTimeMillis();
log.info(String.format("[id = %s] Scan complete [took=%s milliseconds].", id, end - start));
log.fine(String.format("[id = %s] Resolved schemes: %n%s%n", id, schemes));
log.fine(String.format("[id = %s] Generating root liquibase file...", id));
String template = StreamUtil.readStreamAsString(is); // schema.template.xml
String xml = String.format(template, schemes);
FileUtil.write(xml, output);
log.info(String.format("[id = %s] File %s was written.", id, output));
log.fine(String.format("[id = %s] Generation complete.", id));
log.fine(String.format("[id = %s] Root liquibase xml: %n %s %n", id, xml));
CDILiquibaseConfig config = new CDILiquibaseConfig();
config.setChangeLog(TEMPLATE_NAME);
return config;
}
synchronized CDILiquibaseConfig jvmLocked(final String id, Callable action) throws Exception {
return fileLocked(id, action);
}
/**
* Synchronization among multiple JVM's.
*/
@SuppressWarnings("java:S2142") // false positive ignored as per https://sonarsource.atlassian.net/browse/SONARJAVA-4406
CDILiquibaseConfig fileLocked(final String id, Callable action) throws Exception {
log.info(String.format("[id = %s] JVM lock acquired, acquiring file lock", id));
String lockPath = String.format("%s/schema.liquibase.lock", ROOT_PATH);
File lockFile = new File(lockPath);
if (!lockFile.exists() && lockFile.createNewFile()) {
log.info(String.format("[id = %s] Created lock file [path='%s'].", id, lockPath));
}
log.info(String.format("[id = %s] Trying to acquire the file lock [file='%s']...", id, lockPath));
CDILiquibaseConfig actionResult = null;
FileLock lock = null;
try (
FileOutputStream fileStream = new FileOutputStream(lockPath);
FileChannel fileChannel = fileStream.getChannel();
)
{
while (null == lock) {
try {
lock = fileChannel.tryLock();
} catch (OverlappingFileLockException e) {
log.fine(String.format("[id = %s] Lock already acquired, waiting for the lock...", id));
}
if (null == lock) {
log.fine(String.format("[id = %s] Waiting for the lock...", id));
try {
Thread.sleep(FILE_LOCK_TIMEOUT);
} catch (InterruptedException interruptedException) {
log.severe(interruptedException.getMessage(), interruptedException);
Thread.currentThread().interrupt();
}
}
}
log.info(String.format("[id = %s] File lock acquired, running liquibase...", id));
actionResult = action.call();
lock.release();
} catch (Exception e) {
log.warning(e.getMessage(), e);
}
return actionResult;
}
private String copyToFile(final String id, final String liquibase, final String schema) {
log.info(String.format("[id = %s] copyToFile(%s, %s)", id, liquibase, schema));
InputStream is = null;
try {
is = this.getClass().getClassLoader().getResourceAsStream(schema);
log.info(String.format("[id = %s] Transferring schema [resource=%s] to directory [path=%s]...", id, schema, liquibase));
String path = schema.startsWith("/") ? schema.substring(1) : schema;
log.fine(String.format("[id = %s] LiquibaseSchema path is [path='%s'].", id, path));
if (path.contains("/")) {
String dirPath = String.format("%s/%s", liquibase, path.substring(0, path.lastIndexOf('/')));
log.fine(String.format("[id = %s] LiquibaseSchema path contains intermediate directories [path='%s'], preparing its...", id, dirPath));
File file = new File(dirPath);
if (!file.exists() && file.mkdirs()) {
log.info(String.format("[id = %s] Directories for [path='%s'] file created.", id, file.getAbsolutePath()));
}
}
File file = new File(String.format("%s/%s", liquibase, path));
if (file.exists()) {
log.info(String.format("[id = %s] LiquibaseSchema file [path='%s'] already exists, deleting...", id, file.getAbsolutePath()));
if (file.delete()) {
log.info(String.format("[id = %s] File [path='%s'] deleted.", id, file.getAbsolutePath()));
}
}
if (file.createNewFile()) {
log.info(String.format("[id = %s] File [path='%s'] created.", id, file.getAbsolutePath()));
}
log.fine(String.format("[id = %s] LiquibaseSchema file [path='%s'] is ready, copying data...", id, file.getAbsolutePath()));
FileUtil.write(StreamUtil.readStreamAsString(is), file);
String schemaPath = file.getAbsolutePath().replace(ROOT_PATH, "");
log.info(String.format("[id = %s] Data copied, schema path is [path='%s'].", id, schemaPath));
return schemaPath;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException ioe) {
log.warning(String.format("IOException during closing an input stream '%s'.", ioe.getMessage()), ioe);
}
}
}
static class AnnotationLiteralDefault extends AnnotationLiteral {
private static final long serialVersionUID = -2878951947483191L;
}
}