Skip to content

Commit

Permalink
Merge pull request #6558 from liquibase/DAT-19049
Browse files Browse the repository at this point in the history
* [DAT-18890] Implement copy method in FilesystemProjectCopier, change visibility in InitCopyCommandStep.

* [DAT-18890] Enhance error message

* Added stub method in to handle duplicates in sub-commands

DAT-19049

* Refactor code that handles stubs

DAT-19049

* Remove import

DAT-19049

* [DAT-18890] Copy files using copyLarge. Format file.

* Added a stub attribute to CommandStep interface so commands can be recognized as stubs (#6328)

* Added a stub attribute to CommandStep interface

DAT-18431

* Refactored to support stub commands

DAT-18431

* Refactored code to avoid duplication

DAT-18431

---------

Co-authored-by: Alex Brackx <[email protected]>
Co-authored-by: obovsunivskyii <[email protected]>
  • Loading branch information
abrackx and obovsunivskyii authored Dec 11, 2024
2 parents ff2b0c9 + 273c06c commit 903cc39
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,20 +275,28 @@ public Building<DataType> addForcePrintAlias(String alias) {
newCommandArgument.forcePrintedAliases.add(alias);
return this;
}

/**
* Complete construction and register the definition with the rest of the system.
*
* @throws IllegalArgumentException is an invalid configuration was specified
*/
public CommandArgumentDefinition<DataType> build() throws IllegalArgumentException {
return build(false);
}

/**
* Complete construction and register the definition with the rest of the system.
*
* @throws IllegalArgumentException is an invalid configuration was specified
*/
public CommandArgumentDefinition<DataType> build(boolean allowDuplicates) throws IllegalArgumentException {
if (!ALLOWED_ARGUMENT_PATTERN.matcher(newCommandArgument.name).matches()) {
throw new IllegalArgumentException("Invalid argument format: " + newCommandArgument.name);
}

for (String[] commandName : commandNames) {
try {
Scope.getCurrentScope().getSingleton(CommandFactory.class).register(commandName, newCommandArgument);
Scope.getCurrentScope().getSingleton(CommandFactory.class).register(commandName, newCommandArgument, allowDuplicates);
} catch (IllegalArgumentException iae) {
Scope.getCurrentScope().getLog(CommandArgumentDefinition.class).warning(
"Unable to register command '" + StringUtil.join(commandName, " ") + "' argument '" + newCommandArgument.getName() + "': " + iae.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import liquibase.Scope;
import liquibase.SingletonObject;
import liquibase.servicelocator.ServiceLocator;
import liquibase.util.CollectionUtil;
import liquibase.util.DependencyUtil;
import liquibase.util.StringUtil;

Expand Down Expand Up @@ -71,6 +72,7 @@ private void computePipelineForCommandDefinition(CommandDefinition commandDefini
Collection<CommandStep> allCommandStepInstances = findAllInstances();
Map<Class<? extends CommandStep>, CommandStep> overrides = findAllOverrides(allCommandStepInstances);
for (CommandStep step : allCommandStepInstances) {

// order > 0 means is means that this CommandStep has been declared as part of this command
if (step.getOrder(commandDefinition) > 0) {
Optional<CommandStep> overrideStep = getOverride(overrides, step);
Expand Down Expand Up @@ -160,11 +162,17 @@ private void adjustCommandDefinitionForSteps(CommandDefinition commandDefinition
*/
public SortedSet<CommandDefinition> getCommands(boolean includeInternal) {
Map<String, String[]> commandNames = new HashMap<>();
for (CommandStep step : findAllInstances()) {
Set<String> keys = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
Collection<CommandStep> allFoundInstances = findAllInstances();
for (CommandStep step : allFoundInstances) {
String[][] names = step.defineCommandNames();
if (names != null) {
for (String[] name : names) {
commandNames.put(StringUtil.join(name, " "), name);
String key = StringUtil.join(name, " ");
if (! step.isStub() || ! keys.contains(key)) {
commandNames.put(key, name);
keys.add(key);
}
}
}
}
Expand All @@ -184,19 +192,33 @@ public SortedSet<CommandDefinition> getCommands(boolean includeInternal) {
return Collections.unmodifiableSortedSet(commands);

}

/**
* Called by {@link CommandArgumentDefinition.Building#build()} to
* register that a particular {@link CommandArgumentDefinition} is available for a command.
*/
protected void register(String[] commandName, CommandArgumentDefinition<?> definition) {
register(commandName, definition, false);
}

/**
* Called by {@link CommandArgumentDefinition.Building#build()} to
* register that a particular {@link CommandArgumentDefinition} is available for a command.
* This method supports an additional argument which allows duplicate arguments to exist
* without throwing an exception. This is used by stub commands implemented for Pro features
* that do not have their extension present.
*/
protected void register(String[] commandName, CommandArgumentDefinition<?> definition, boolean allowDuplicates) {
String commandNameKey = StringUtil.join(commandName, " ");
if (!commandArgumentDefinitions.containsKey(commandNameKey)) {
commandArgumentDefinitions.put(commandNameKey, new TreeSet<>());
}

if (commandArgumentDefinitions.get(commandNameKey).contains(definition)) {
throw new IllegalArgumentException("Duplicate argument '" + definition.getName() + "' found for command '" + commandNameKey + "'");
if (! allowDuplicates) {
throw new IllegalArgumentException("Duplicate argument '" + definition.getName() + "' found for command '" + commandNameKey + "'");
}
return;
}
if (definition.isRequired() && definition.getDefaultValue() != null) {
throw new IllegalArgumentException("Argument '" + definition.getName() + "' for command '" + commandNameKey + "' has both a default value and the isRequired flag set to true. Arguments with default values cannot be marked as required.");
Expand All @@ -206,9 +228,9 @@ protected void register(String[] commandName, CommandArgumentDefinition<?> defin

/**
* Unregisters all information about the given {@link CommandStep}.
* <bNOTE:</b> package-protected method used primarily for testing and may be removed or modified in the future.
* This is used to handle a situation where we have multiple command step instances
*/
protected void unregister(String[] commandName) {
public void unregister(String[] commandName) {
commandArgumentDefinitions.remove(StringUtil.join(commandName, " "));
}

Expand Down Expand Up @@ -253,14 +275,38 @@ public void resetCommandDefinitions() {
private synchronized Collection<CommandStep> findAllInstances() {
if (this.allInstances == null) {
this.allInstances = new ArrayList<>();

ServiceLocator serviceLocator = Scope.getCurrentScope().getServiceLocator();
this.allInstances.addAll(serviceLocator.findInstances(CommandStep.class));
filterStubInstances();
}

return this.allInstances;
}

//
// Sort all command stubs to the bottom of the list so that any non-stubs
// will come first. If a stub is found and the non-stub is present, the stub
// is removed from the list.
//
private void filterStubInstances() {
((List<CommandStep>) this.allInstances).sort(Comparator.comparing(CommandStep::isStub));
Set<String> commandNames = new LinkedHashSet<>();
Collection<CommandStep> toRemove = new ArrayList<>();
for (CommandStep step : this.allInstances) {
String[][] names = step.defineCommandNames();
if (names != null) {
for (String[] name : names) {
String key = StringUtil.join(name, " ").toLowerCase();
if (step.isStub() && commandNames.contains(key)) {
toRemove.add(step);
} else {
commandNames.add(key);
}
}
}
}
this.allInstances.removeAll(toRemove);
}

/**
* Find all commands that override other commands based on {@link CommandOverride#override()}.
* Validates that only a single command is overriding another.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,13 @@ public interface CommandStep {
* @return list with the provided classes types
*/
List<Class<?>> providedDependencies();

/**
* Returns a boolean to indicate that this CommandStep is a stub
* If an extension overrides the CommandStep then the stub sill be ignored
* @return boolean
*/
default boolean isStub() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package liquibase.command.copy;

import liquibase.exception.UnexpectedLiquibaseException;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FileSystemProjectCopier implements ProjectCopier {
@Override
Expand All @@ -27,38 +30,39 @@ public boolean isRemote() {
}

/**
*
* Create the project directory if it does not exist
* For the local file system implementation of ProjectCopier this will not
* be a temporary directory, so the keepTempFiles flag is ignored
*
* @param projectDir The project directory
* @param keepTempFiles *** IGNORED ***
* @return File
*
* @param projectDir The project directory
* @param keepTempFiles *** IGNORED ***
* @return File
*/
@Override
public File createWorkingStorage(String projectDir, boolean keepTempFiles) {
File projectDirFile = new File(projectDir);
boolean b = projectDirFile.mkdirs();
if (! b && ! projectDirFile.exists()) {
if (!b && !projectDirFile.exists()) {
throw new UnexpectedLiquibaseException("Unable to create project directory '" + projectDir + "'");
}
return projectDirFile;
}

/**
*
* Copy files from the source to the remote location
* This is a no-op currently for this implementation of ProjectCopier
*
* @param source The source directory
* @param target The target directory
* @param recursive Flag for copying recursively
*
* @param source The source directory
* @param target The target directory
* @param recursive Flag for copying recursively
*/
@Override
public void copy(String source, String target, boolean recursive) {
throw new UnexpectedLiquibaseException("The command 'init copy' requires s3:// paths and cannot be used with local file system paths. Learn more at https://docs.liquibase.com/commands/init/copy.html");
try (InputStream input = Files.newInputStream(Paths.get(source));
OutputStream output = Files.newOutputStream(Paths.get(target))) {
IOUtils.copyLarge(input, output);
} catch (Exception e) {
throw new UnexpectedLiquibaseException("Unable to copy file(s)! Make sure you are targeting a valid path for the new file.", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void adjustCommandDefinition(CommandDefinition commandDefinition) {
//
// Use the internal ProjectCopier if available otherwise retrieve a new one
//
private ProjectCopier determineProjectCopier(CommandScope commandScope, String targetDir) {
public static ProjectCopier determineProjectCopier(CommandScope commandScope, String targetDir) {
ProjectCopier copier = commandScope.getConfiguredValue(INIT_COPY_PROJECT_COPIER_ARG).getValue();
if (copier != null) {
return copier;
Expand Down

0 comments on commit 903cc39

Please sign in to comment.