liquibase.integration.jakarta.cdi.SchemesTreeBuilder 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import jakarta.enterprise.context.ApplicationScoped;
import liquibase.Scope;
import liquibase.integration.jakarta.cdi.annotations.LiquibaseSchema;
import liquibase.integration.jakarta.cdi.exceptions.CyclicDependencyException;
import liquibase.integration.jakarta.cdi.exceptions.DependencyNotFoundException;
import liquibase.logging.Logger;
/**
* @author Nikita Lipatov (https://github.com/islonik), antoermo (https://github.com/dikeert)
* @since 27/5/17.
*/
@ApplicationScoped
public class SchemesTreeBuilder {
private static final Logger log = Scope.getCurrentScope().getLog(SchemesTreeBuilder.class);
private class SchemaNode {
private final LiquibaseSchema item;
private final Collection children = new ArrayList<>();
public SchemaNode(LiquibaseSchema item) {
this.item = item;
}
public LiquibaseSchema getItem() {
return item;
}
public Collection getChildren() {
return Collections.unmodifiableCollection(children);
}
public void addChild(LiquibaseSchema child) {
children.add(new SchemaNode(child));
}
public SchemaNode find(String name) {
SchemaNode result = null;
if (this.item.name().equals(name)) {
result = this;
} else {
for (SchemaNode child : children) {
SchemaNode found = child.find(name);
if ((result == null) && (found != null)) {
result = child.find(name);
} else if ((result != null) && (found != null)) {
throw new IllegalStateException(String.format(
"Duplicate schema names [%s] detected!",
result.getItem().name()));
}
}
}
return result;
}
public List toList() {
List list = new ArrayList<>(children.size() + 1);
list.add(item);
for (SchemaNode child : children) {
list.addAll(child.toList());
}
return list;
}
}
/**
* Builds a collection of schemes sorted according dependencies
*
* @param schemes All found Liquibase Schema annotations in 'war' or 'ear' type file.
* @return sorted collection of schemes
*/
public List build(final String id, Collection schemes) {
log.fine(String.format("[id = %s] build(%s)", id, schemes));
log.info(String.format("[id = %s] Sorting schemes according dependencies...", id));
if (schemes.isEmpty()) {
return Collections.emptyList();
}
SchemaNode root = null;
// first, copy schemes to no modify source collection
schemes = new ArrayList<>(schemes);
Collection availableSchemes = new ArrayList<>(schemes);
// then find not dependent schemes - this will the roots of hierarchy.
List notDependent = new ArrayList<>();
for (LiquibaseSchema liquibaseSchema : schemes) {
String depends = liquibaseSchema.depends();
if (depends.trim().isEmpty()) {
notDependent.add(liquibaseSchema);
}
}
log.info(String.format("[id = %s] Found [%s] not dependent schemes.", id, notDependent.size()));
if (notDependent.isEmpty()) { // if there is no not-dependent schema, then there is a cyclic dependency.
throw new CyclicDependencyException(String.format("[id = %s] Not independent schemes, possible cyclic dependencies discovered.", id));
} else {
// take first of not-dependent and use it as root of hierarchy.
root = new SchemaNode(notDependent.get(0));
log.fine(String.format("[id = %s] Selected dependencies tree root [%s]", id, root.getItem()));
availableSchemes.removeAll(notDependent); // we won't to check not-dependent schemes.
notDependent.remove(root.getItem()); // remove root from not-dependent schemes
schemes.retainAll(availableSchemes); // remove not-dependent from all schemes
// now make all not-dependent schemes children to selected root.
for (LiquibaseSchema liquibaseSchema : notDependent) {
root.addChild(liquibaseSchema);
}
log.fine(String.format("[id = %s] Made other non-dependent schemes children of root. [%s] dependent schemes to resolve. Resolving...",
id,
availableSchemes.size()
));
int cycles = 0;
long start = System.currentTimeMillis();
// until we resolve all dependencies
while (!availableSchemes.isEmpty()) {
cycles++;
log.fine(String.format("[id = %s] Resolution cycle [%s] started.", id, cycles));
int additions = 0; //we will count dependencies resolution for each resolution cycle.
for (LiquibaseSchema liquibaseSchema : schemes) {
log.fine(String.format(
"[id = %s] LiquibaseSchema [name=%s] depends on liquibaseSchema [name=%s].",
id, liquibaseSchema.name(), liquibaseSchema.depends()
));
SchemaNode parent = root.find(liquibaseSchema.depends());
// we make the dependent liquibaseSchema as a child for it's dependency if found. If not, we just continue.
if (parent == null) {
log.fine(String.format(
"[id = %s] Dependency not found in resolved dependencies tree, skipping liquibaseSchema [name=%s] for a while.",
id, liquibaseSchema.name()
));
boolean isDependencyMissed = true;
for (LiquibaseSchema tmpLiquibaseSchema : availableSchemes) {
if (tmpLiquibaseSchema.name().equalsIgnoreCase(liquibaseSchema.depends())) {
isDependencyMissed = false;
break;
}
}
if (isDependencyMissed) {
throw new DependencyNotFoundException(String.format(
"[id = %s][name=%s] depends on [name=%s], but it is not found!",
id, liquibaseSchema.name(), liquibaseSchema.depends()
));
}
} else {
log.fine(String.format(
"[id = %s] Dependency found for liquibaseSchema [name=%s], moving it to resolved dependencies tree.",
id, liquibaseSchema.name()
));
parent.addChild(liquibaseSchema);
availableSchemes.remove(liquibaseSchema);
additions++;
}
}
log.fine(String.format("[id = %s] Resolution cycle [%s] completed", id, cycles));
//if not resolutions happened through resolution cycle, definitely there is a cyclic dependency.
if (additions == 0) {
throw new CyclicDependencyException(String.format("[id = %s] Cyclic dependencies discovered!", id));
}
schemes.retainAll(availableSchemes);
}
log.info(String.format("[id = %s] Dependencies resolved in [cycles=%s, millis=%s]", id, cycles, System.currentTimeMillis() - start));
}
return root.toList();
}
}