package org.jboss.windup.ui; import java.io.File; import java.nio.file.Path; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import javax.inject.Inject; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jboss.forge.addon.resource.DirectoryResource; import org.jboss.forge.addon.resource.FileResource; import org.jboss.forge.addon.resource.Resource; import org.jboss.forge.addon.resource.ResourceFactory; import org.jboss.forge.addon.resource.util.ResourcePathResolver; import org.jboss.forge.addon.ui.UIProvider; import org.jboss.forge.addon.ui.command.UICommand; import org.jboss.forge.addon.ui.context.UIBuilder; import org.jboss.forge.addon.ui.context.UIContext; import org.jboss.forge.addon.ui.context.UIExecutionContext; import org.jboss.forge.addon.ui.context.UIValidationContext; import org.jboss.forge.addon.ui.input.InputComponent; import org.jboss.forge.addon.ui.input.InputComponentFactory; import org.jboss.forge.addon.ui.input.UIInput; import org.jboss.forge.addon.ui.input.UIInputMany; import org.jboss.forge.addon.ui.input.UISelectMany; import org.jboss.forge.addon.ui.input.UISelectOne; import org.jboss.forge.addon.ui.metadata.UICommandMetadata; import org.jboss.forge.addon.ui.output.UIOutput; import org.jboss.forge.addon.ui.progress.UIProgressMonitor; import org.jboss.forge.addon.ui.result.Result; import org.jboss.forge.addon.ui.result.Results; import org.jboss.forge.addon.ui.util.Categories; import org.jboss.forge.addon.ui.util.Metadata; import org.jboss.forge.addon.ui.validate.UIValidator; import org.jboss.windup.config.ConfigurationOption; import org.jboss.windup.config.ValidationResult; import org.jboss.windup.exec.WindupProcessor; import org.jboss.windup.exec.WindupProgressMonitor; import org.jboss.windup.exec.configuration.WindupConfiguration; import org.jboss.windup.exec.configuration.options.InputPathOption; import org.jboss.windup.exec.configuration.options.OutputPathOption; import org.jboss.windup.exec.configuration.options.OverwriteOption; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.GraphContextFactory; /** * Provides a basic Forge UI implementation for running Windup from within a {@link UIProvider}. * * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a> * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a> * @author <a href="http://ondra.zizka.cz/">Ondrej Zizka, ozizka at seznam.cz</a> */ public class WindupCommand implements UICommand { private Map<ConfigurationOption, InputComponent<?, ?>> inputOptions = new LinkedHashMap<>(); @Inject private InputComponentFactory componentFactory; @Inject private GraphContextFactory graphContextFactory; @Inject private WindupProcessor processor; @Inject private ResourceFactory resourceFactory; /* * Using a Set because messages seem to be added multiple times by validation. Need to look into this. */ private Set<ValidationResult> promptMessages = new HashSet<>(); @Override public UICommandMetadata getMetadata(UIContext ctx) { return Metadata.forCommand(getClass()).name("Windup Migrate App").description("Run Windup Migration Analyzer") .category(Categories.create("Platform", "Migration")); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void initializeUI(UIBuilder builder) throws Exception { initializeConfigurationOptionComponents(builder); final UIInputMany inputPaths = (UIInputMany) getInputForOption(InputPathOption.class); final UIInput outputPath = (UIInput) getInputForOption(OutputPathOption.class); outputPath.setDefaultValue(new Callable<DirectoryResource>() { @Override public DirectoryResource call() throws Exception { Iterable<File> inputPathsIterable = inputPaths.getValue(); if (inputPathsIterable.iterator().hasNext()) { // set the default based on the first one File value = inputPathsIterable.iterator().next(); File childDirectory = new File(value.getCanonicalFile().getParentFile(), value.getName() + ".report"); return resourceFactory.create(DirectoryResource.class, childDirectory); } return null; } }); outputPath.addValidator(new UIValidator() { @Override public void validate(UIValidationContext context) { File outputFile = (File) getValueForInput(getOption(OutputPathOption.NAME), outputPath); final UIInputMany inputPathsUI = (UIInputMany) getInputForOption(InputPathOption.class); Iterable<File> inputPathsIterable = inputPathsUI.getValue(); /** * It would be really nice to be able to use native Resource types here... but we can't "realllly" * do that because the Windup configuration API doesn't understand Forge data types, so instead we * use string comparison and write a test case. */ //File inputFile = (File) inputPath.getUnderlyingResourceObject(); for (File inputFile : inputPathsIterable){ if (inputFile.equals(outputFile)) context.addValidationError(outputPath, "Output file cannot be the same as the input file."); File inputParent = inputFile.getParentFile(); while (inputParent != null) { if (inputParent.equals(outputFile)) context.addValidationError(outputPath, "Output path must not be a parent of input path."); inputParent = inputParent.getParentFile(); } File outputParent = outputFile.getParentFile(); while (outputParent != null) { if (outputParent.equals(inputFile)) context.addValidationError(inputPathsUI, "Input path must not be a parent of output path."); outputParent = outputParent.getParentFile(); } } } }); } @Override public void validate(UIValidationContext context) { for (Entry<ConfigurationOption, InputComponent<?, ?>> entry : this.inputOptions.entrySet()) { final InputComponent<?, ?> inputComponent = entry.getValue(); ConfigurationOption option = entry.getKey(); Object inputValue = getValueForInput(option, inputComponent); if (inputValue == null && !option.isRequired()) return; ValidationResult result = option.validate(inputValue); switch (result.getLevel()) { case ERROR: context.addValidationError(inputComponent, result.getMessage()); break; case WARNING: context.addValidationWarning(inputComponent, result.getMessage()); break; case PROMPT_TO_CONTINUE: this.promptMessages.add(result); break; } } } @Override public Result execute(UIExecutionContext context) throws Exception { if (!this.promptMessages.isEmpty()) { for (ValidationResult message : promptMessages) { UIOutput output = context.getUIContext().getProvider().getOutput(); output.warn(output.out(), message.getMessage()); } if (context.getPrompt().promptBoolean("Would you like to continue?", true) == false) { return Results.fail("Aborted by the user."); } } WindupConfiguration windupConfiguration = new WindupConfiguration(); for (Entry<ConfigurationOption, InputComponent<?, ?>> entry : this.inputOptions.entrySet()) { ConfigurationOption option = entry.getKey(); String name = option.getName(); Object value = getValueForInput(option, entry.getValue()); windupConfiguration.setOptionValue(name, value); } windupConfiguration.useDefaultDirectories(); Boolean overwrite = (Boolean) windupConfiguration.getOptionMap().get(OverwriteOption.NAME); if (overwrite == null) { overwrite = false; } if (!overwrite && pathNotEmpty(windupConfiguration.getOutputDirectory().toFile())) { String promptMsg = "Overwrite all contents of \"" + windupConfiguration.getOutputDirectory().toString() + "\" (anything already in the directory will be deleted)?"; if (!context.getPrompt().promptBoolean(promptMsg, false)) { String outputPath = windupConfiguration.getOutputDirectory().toString(); return Results.fail("Files exist in " + outputPath + ", but --overwrite not specified. Aborting!"); } } /* * Put this in the context for debugging, and unit tests (or anything else that needs it). */ context.getUIContext().getAttributeMap().put(WindupConfiguration.class, windupConfiguration); FileUtils.deleteQuietly(windupConfiguration.getOutputDirectory().toFile()); Path graphPath = windupConfiguration.getOutputDirectory().resolve("graph"); try (GraphContext graphContext = graphContextFactory.create(graphPath)) { context.getUIContext().getAttributeMap().put(GraphContext.class, graphContext); UIProgressMonitor uiProgressMonitor = context.getProgressMonitor(); WindupProgressMonitor progressMonitor = new WindupProgressMonitorAdapter(uiProgressMonitor); windupConfiguration .setProgressMonitor(progressMonitor) .setGraphContext(graphContext); processor.execute(windupConfiguration); uiProgressMonitor.done(); Path indexHtmlPath = windupConfiguration.getOutputDirectory().resolve("index.html").normalize().toAbsolutePath(); return Results.success("Report created: " + indexHtmlPath + System.getProperty("line.separator") + " Access it at this URL: " + indexHtmlPath.toUri()); } } @Override public boolean isEnabled(UIContext context) { return true; } /* * Utility methods */ @SuppressWarnings({ "unchecked", "rawtypes" }) private void initializeConfigurationOptionComponents(UIBuilder builder) { for (final ConfigurationOption option : WindupConfiguration.getWindupConfigurationOptions()) { InputComponent<?, ?> inputComponent = null; switch (option.getUIType()) { case SINGLE: { UIInput<?> inputSingle = componentFactory.createInput(option.getName(), option.getType()); inputSingle.setDefaultValue(new DefaultValueAdapter(option)); inputComponent = inputSingle; break; } case MANY: { // forge can't handle "Path", so use File Class<?> optionType = option.getType() == Path.class ? File.class : option.getType(); UIInputMany<?> inputMany = componentFactory.createInputMany(option.getName(), optionType); inputMany.setDefaultValue(new DefaultValueAdapter(option, Iterable.class)); inputComponent = inputMany; break; } case SELECT_MANY: { UISelectMany<?> selectMany = componentFactory.createSelectMany(option.getName(), option.getType()); selectMany.setValueChoices((Iterable) option.getAvailableValues()); selectMany.setDefaultValue(new DefaultValueAdapter(option, Iterable.class)); inputComponent = selectMany; break; } case SELECT_ONE: { UISelectOne<?> selectOne = componentFactory.createSelectOne(option.getName(), option.getType()); selectOne.setValueChoices((Iterable) option.getAvailableValues()); selectOne.setDefaultValue(new DefaultValueAdapter(option)); inputComponent = selectOne; break; } case DIRECTORY: { UIInput<DirectoryResource> directoryInput = componentFactory.createInput(option.getName(), DirectoryResource.class); directoryInput.setDefaultValue(new DefaultValueAdapter(option, DirectoryResource.class)); inputComponent = directoryInput; break; } case FILE: { UIInput<?> fileInput = componentFactory.createInput(option.getName(), FileResource.class); fileInput.setDefaultValue(new DefaultValueAdapter(option, FileResource.class)); inputComponent = fileInput; break; } case FILE_OR_DIRECTORY: { UIInput<?> fileOrDirInput = componentFactory.createInput(option.getName(), FileResource.class); fileOrDirInput.setDefaultValue(new DefaultValueAdapter(option, FileResource.class)); inputComponent = fileOrDirInput; break; } } if (inputComponent == null) { throw new IllegalArgumentException("Could not build input component for: " + option); } inputComponent.setLabel(option.getLabel()); inputComponent.setRequired(option.isRequired()); inputComponent.setDescription(option.getDescription()); builder.add(inputComponent); inputOptions.put(option, inputComponent); } } private ConfigurationOption getOption(String name) { for (Entry<ConfigurationOption, InputComponent<?, ?>> entry : this.inputOptions.entrySet()) { if (StringUtils.equals(name, entry.getKey().getName())) { return entry.getKey(); } } return null; } private InputComponent<?, ?> getInputForOption(Class<? extends ConfigurationOption> option) { for (Entry<ConfigurationOption, InputComponent<?, ?>> entry : this.inputOptions.entrySet()) { if (option.isAssignableFrom(entry.getKey().getClass())) { return entry.getValue(); } } return null; } private Object getValueForInput(ConfigurationOption option, InputComponent<?, ?> input) { Object value = input.getValue(); if (value == null) { return value; } if (value instanceof Resource<?>) { Resource<?> resourceResolved = getResourceResolved((Resource<?>) value); return resourceResolved.getUnderlyingResourceObject(); } if (option.getType() == Path.class) { // these have to be converted Set<Path> paths = new LinkedHashSet<>(); if (value instanceof List) { for (Object path : (List) value) { if (path instanceof File) path = ((File) path).toPath(); paths.add((Path) path); } } value = paths; } return value; } private Resource<?> getResourceResolved(Resource<?> value) { Resource<?> resource = (Resource<?>) value; File file = (File) resource.getUnderlyingResourceObject(); return new ResourcePathResolver(resourceFactory, resource, file.getPath()).resolve().get(0); } private boolean pathNotEmpty(File f) { if (f.exists() && !f.isDirectory()) { return true; } if (f.isDirectory() && f.listFiles() != null && f.listFiles().length > 0) { return true; } return false; } }