package org.jboss.windup.bootstrap.commands.windup; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jboss.forge.furnace.Furnace; import org.jboss.windup.bootstrap.Bootstrap; import org.jboss.windup.bootstrap.ConsoleProgressMonitor; import org.jboss.windup.bootstrap.commands.Command; import org.jboss.windup.bootstrap.commands.CommandPhase; import org.jboss.windup.bootstrap.commands.CommandResult; import org.jboss.windup.bootstrap.commands.FurnaceDependent; import org.jboss.windup.config.ConfigurationOption; import org.jboss.windup.config.InputType; import org.jboss.windup.config.KeepWorkDirsOption; import org.jboss.windup.config.SkipReportsRenderingOption; import org.jboss.windup.config.ValidationResult; import org.jboss.windup.config.metadata.RuleProviderRegistryCache; 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.ExplodedAppInputOption; 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.exec.configuration.options.TargetOption; import org.jboss.windup.exec.configuration.options.UserRulesDirectoryOption; import org.jboss.windup.graph.GraphContextFactory; import org.jboss.windup.rules.apps.java.config.ExcludePackagesOption; import org.jboss.windup.rules.apps.java.config.ScanPackagesOption; import org.jboss.windup.rules.apps.java.config.SourceModeOption; import org.jboss.windup.util.Logging; import org.jboss.windup.util.exception.WindupException; /** * This is the interactive command-line user interface of Windup * which does basic validation of the input and then runs WindupProcessorImpl. */ public class RunWindupCommand implements Command, FurnaceDependent { private static final Logger log = Logging.get(RunWindupCommand.class); private Furnace furnace; private final List<String> arguments; private final AtomicBoolean batchMode; public RunWindupCommand(List<String> arguments, AtomicBoolean batchMode) { this.arguments = arguments; this.batchMode = batchMode; } @Override public CommandResult execute() { runWindup(arguments); return CommandResult.EXIT; } @Override public void setFurnace(Furnace furnace) { this.furnace = furnace; } @Override public CommandPhase getPhase() { return CommandPhase.EXECUTION; } @SuppressWarnings("unchecked") private void runWindup(List<String> arguments) { Iterable<ConfigurationOption> optionIterable = WindupConfiguration.getWindupConfigurationOptions(furnace); Map<String, ConfigurationOption> options = new HashMap<>(); for (ConfigurationOption option : optionIterable) options.put(option.getName().toUpperCase(), option); Map<String, Object> optionValues = new HashMap<>(); for (int i = 0; i < arguments.size(); i++) { String argument = arguments.get(i); String optionName = getOptionName(argument); if (optionName == null) { System.err.println("WARNING: Unrecognized command-line argument: " + argument); continue; } ConfigurationOption option = options.get(optionName.toUpperCase()); if (option == null) { System.err.println("WARNING: Unrecognized command-line argument: " + argument); continue; } // For MANY InputType, take the following arguments as values. if (option.getUIType() == InputType.MANY || option.getUIType() == InputType.SELECT_MANY) { List<Object> values = new ArrayList<>(); i++; while (i < arguments.size()) { final String arg = arguments.get(i); final String name = getOptionName(arg); if (name != null) { // This is the next parameter... back up one and break the loop. i--; break; } String valueString = arguments.get(i); // Lists are space delimited. split them here. for (String value : StringUtils.split(valueString, ' ')) values.add(convertType(option.getType(), value)); i++; } /* * This allows us to support specifying a parameter multiple times. For example: `windup --packages foo --packages bar --packages baz` * While this is not necessarily the recommended approach, it would be nice for it to work smoothly if someone does it this way. */ if (optionValues.containsKey(option.getName())) ((List<Object>) optionValues.get(option.getName())).addAll(values); else optionValues.put(option.getName(), values); } else if (Boolean.class.isAssignableFrom(option.getType())) { optionValues.put(option.getName(), true); } else { String valueString = arguments.get(++i); Object value = convertType(option.getType(), valueString); optionValues.put(option.getName(), value); } } setDefaultOutputPath(optionValues); setDefaultOptionsValues(options, optionValues); RuleProviderRegistryCache ruleProviderRegistryCache = furnace.getAddonRegistry().getServices(RuleProviderRegistryCache.class).get(); File userProvidedPath = (File) optionValues.get(UserRulesDirectoryOption.NAME); if (userProvidedPath != null) { ruleProviderRegistryCache.addUserRulesPath(userProvidedPath.toPath()); } // Target - interactive Collection<String> targets = (Collection<String>) optionValues.get(TargetOption.NAME); if ((targets == null || targets.isEmpty()) && !batchMode.get()) { String target = Bootstrap.promptForListItem("Please select a target:", ruleProviderRegistryCache.getAvailableTargetTechnologies(), "eap"); targets = Collections.singleton(target); optionValues.put(TargetOption.NAME, targets); } boolean validationSuccess = validateOptionValues(options, optionValues); if (!validationSuccess) return; // In case of --unzippedAppInput or --sourceMode, treat the directories in --input as unzipped applications. // Otherwise, as a directory containing separate applications (default). Boolean isExplodedApp = (Boolean) optionValues.get(ExplodedAppInputOption.NAME) || (Boolean) optionValues.get(SourceModeOption.NAME); if (!isExplodedApp) { List<Path> input = (List<Path>) optionValues.get(InputPathOption.NAME); input = expandMultiAppInputDirs(input); optionValues.put(InputPathOption.NAME, input); } WindupConfiguration windupConfiguration = new WindupConfiguration(); for (Map.Entry<String, ConfigurationOption> optionEntry : options.entrySet()) { ConfigurationOption option = optionEntry.getValue(); windupConfiguration.setOptionValue(option.getName(), optionValues.get(option.getName())); } if (!validateInputAndOutputPath(windupConfiguration.getInputPaths(), windupConfiguration.getOutputDirectory())) return; try { windupConfiguration.useDefaultDirectories(); } catch (IOException e) { System.err.println("ERROR: Failed to create default directories due to: " + e.getMessage()); return; } 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 (!Bootstrap.prompt(promptMsg, false, batchMode.get())) { String outputPath = windupConfiguration.getOutputDirectory().toString(); System.err.println("Files exist in " + outputPath + ", but --overwrite not specified. Aborting!"); return; } } FileUtils.deleteQuietly(windupConfiguration.getOutputDirectory().toFile()); Path graphPath = windupConfiguration.getOutputDirectory().resolve(GraphContextFactory.DEFAULT_GRAPH_SUBDIRECTORY); System.out.println(); if (windupConfiguration.getInputPaths().size() == 1) { System.out.println("Input Application:" + windupConfiguration.getInputPaths().iterator().next()); } else { System.out.println("Input Applications:"); for (Path inputPath : windupConfiguration.getInputPaths()) { System.out.println("\t" + inputPath); } System.out.println(); } System.out.println("Output Path:" + windupConfiguration.getOutputDirectory()); System.out.println(); normalizePackagePrefixes(windupConfiguration); try { WindupProgressMonitor progressMonitor = new ConsoleProgressMonitor(); windupConfiguration.setProgressMonitor(progressMonitor); // Run Windup getWindupProcessor().execute(windupConfiguration); final Boolean skipReports = (Boolean) windupConfiguration.getOptionMap().get(SkipReportsRenderingOption.NAME); if (!skipReports) { Path indexHtmlPath = windupConfiguration.getOutputDirectory().resolve("index.html").normalize().toAbsolutePath(); System.out.println("Report created: " + indexHtmlPath + System.getProperty("line.separator") + " Access it at this URL: " + indexHtmlPath.toUri()); } else { System.out.println("Generating reports were disabled by option --skipReports"); System.out.println("If using that option was unintentional, please run RHAMT again to generate reports."); } } catch (Exception e) { System.err.println("Execution failed due to: " + e.getMessage()); e.printStackTrace(); } deleteGraphDataUnlessInhibited(windupConfiguration, graphPath); } private boolean validateInputAndOutputPath(Collection<Path> inputPaths, Path outputPath) { ValidationResult validationResult = OutputPathOption.validateInputsAndOutputPaths(inputPaths, outputPath); switch (validationResult.getLevel()) { case ERROR: System.err.println("ERROR: " + validationResult.getMessage()); return false; case WARNING: System.err.println("WARNING: " + validationResult.getMessage()); default: return true; } } private boolean validateOptionValues(Map<String, ConfigurationOption> options, Map<String, Object> optionValues) { for (Map.Entry<String, ConfigurationOption> optionEntry : options.entrySet()) { ConfigurationOption option = optionEntry.getValue(); ValidationResult result = option.validate(optionValues.get(option.getName())); switch (result.getLevel()) { case ERROR: System.err.println("ERROR: " + result.getMessage()); return false; case PROMPT_TO_CONTINUE: if (!Bootstrap.prompt(result.getMessage(), result.getPromptDefault(), batchMode.get())) return false; break; case WARNING: System.err.println("WARNING: " + result.getMessage()); break; case SUCCESS: break; } } return true; } private void setDefaultOutputPath(Map<String, Object> optionValues) { if (!optionValues.containsKey(OutputPathOption.NAME)) { Iterable<Path> paths = (Iterable<Path>) optionValues.get(InputPathOption.NAME); if (paths != null && paths.iterator().hasNext()) { try { File canonicalInputFile = paths.iterator().next().toFile().getCanonicalFile(); File outputFile = new File(canonicalInputFile.getParentFile(), canonicalInputFile.getName() + ".report"); optionValues.put(OutputPathOption.NAME, outputFile); } catch (IOException e) { throw new WindupException("Failed to get canonical path for input file: " + paths.iterator().next().toFile()); } } } } private Object convertType(Class<?> type, String input) { if (Path.class.isAssignableFrom(type)) { return Paths.get(input); } else if (File.class.isAssignableFrom(type)) { return new File(input); } else if (Boolean.class.isAssignableFrom(type)) { return Boolean.valueOf(input); } else if (String.class.isAssignableFrom(type)) { return input; } else { throw new RuntimeException("Internal Error! Unrecognized type " + type.getCanonicalName()); } } 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; } private WindupProcessor getWindupProcessor() { return furnace.getAddonRegistry().getServices(WindupProcessor.class).get(); } private GraphContextFactory getGraphContextFactory() { return furnace.getAddonRegistry().getServices(GraphContextFactory.class).get(); } private String getOptionName(String argument) { if (argument.startsWith("--")) return argument.substring(2); else if (argument.startsWith("-")) return argument.substring(1); else return null; } private void deleteGraphDataUnlessInhibited(WindupConfiguration windupConfiguration, Path graphPath) { Boolean keep = (Boolean) windupConfiguration.getOptionMap().get(KeepWorkDirsOption.NAME); if (keep == null || !keep) { log.info("Deleting graph directory (see --" + KeepWorkDirsOption.NAME + "): " + graphPath.toFile().getPath()); try { FileUtils.deleteDirectory(graphPath.toFile()); } catch (IOException ex) { log.log(Level.WARNING, "Failed deleting graph directory: " + graphPath.toFile().getPath() + "\n\tDue to: " + ex.getMessage(), ex); } } } /** * Expands the directories from the given list and and returns a list of subfiles. * Files from the original list are kept as is. */ private static List<Path> expandMultiAppInputDirs(List<Path> input) { List<Path> expanded = new LinkedList<>(); for (Path path : input) { if (Files.isRegularFile(path)) { expanded.add(path); continue; } if (!Files.isDirectory(path)) { log.warning("Neither a file or directory found in input: " + path.toString()); continue; } try { try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path)) { for (Path subpath : directoryStream) { expanded.add(subpath); } } } catch (IOException e) { throw new WindupException("Failed to read directory contents of: " + path); } } return expanded; } private void setDefaultOptionsValues(Map<String, ConfigurationOption> options, Map<String, Object> optionValues) { for (Map.Entry<String, ConfigurationOption> option : options.entrySet()) { if (null != optionValues.get(option.getValue().getName())) continue; optionValues.put(option.getValue().getName(), option.getValue().getDefaultValue()); } } /** * Removes the .* suffix from the include and exclude packages input. */ private void normalizePackagePrefixes(WindupConfiguration windupConfiguration) { List<String> includePackages = windupConfiguration.getOptionValue(ScanPackagesOption.NAME); includePackages = normalizePackagePrefixes(includePackages); windupConfiguration.setOptionValue(ScanPackagesOption.NAME, includePackages); List<String> excludePackages = windupConfiguration.getOptionValue(ExcludePackagesOption.NAME); excludePackages = normalizePackagePrefixes(excludePackages); windupConfiguration.setOptionValue(ExcludePackagesOption.NAME, excludePackages); } /** * Removes the .* suffix, which is expectable the users will use. */ private static List<String> normalizePackagePrefixes(List<String> packages) { if (packages == null) return null; List<String> result = new ArrayList<>(packages.size()); for (String pkg : packages) { if(pkg.endsWith(".*")){ System.out.println("Warning: removing the .* suffix from the package prefix: " + pkg); } result.add(StringUtils.removeEndIgnoreCase(pkg, ".*")); } return packages; } }