diff --git a/config/api/src/main/java/org/jboss/windup/config/ExportZipOutputOption.java b/config/api/src/main/java/org/jboss/windup/config/ExportZipOutputOption.java new file mode 100644 index 0000000000..dcb88506f8 --- /dev/null +++ b/config/api/src/main/java/org/jboss/windup/config/ExportZipOutputOption.java @@ -0,0 +1,49 @@ +package org.jboss.windup.config; + +import org.jboss.windup.graph.model.WindupConfigurationModel; +import org.jboss.windup.util.ThemeProvider; + +/** + * Indicates output path will be compressed in a ZIP file + * + * @author Marco Rizzi + */ +public class ExportZipOutputOption extends AbstractConfigurationOption { + public static final String NAME = WindupConfigurationModel.EXPORT_ZIP_REPORT; + + @Override + public String getDescription() { + return "If set, " + ThemeProvider.getInstance().getTheme().getBrandNameAcronym() + " will create a ZIP file containing the files from the output path."; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getLabel() { + return "Should " + ThemeProvider.getInstance().getTheme().getBrandNameAcronym() + " create a ZIP file with the file in the output path?"; + } + + @Override + public Class getType() { + return Boolean.class; + } + + @Override + public InputType getUIType() { + return InputType.SINGLE; + } + + @Override + public boolean isRequired() { + return false; + } + + @Override + public ValidationResult validate(Object value) { + return ValidationResult.SUCCESS; + } + +} diff --git a/exec/api/src/main/java/org/jboss/windup/exec/configuration/WindupConfiguration.java b/exec/api/src/main/java/org/jboss/windup/exec/configuration/WindupConfiguration.java index c21c74f718..fba72dd70f 100644 --- a/exec/api/src/main/java/org/jboss/windup/exec/configuration/WindupConfiguration.java +++ b/exec/api/src/main/java/org/jboss/windup/exec/configuration/WindupConfiguration.java @@ -11,6 +11,7 @@ import org.jboss.forge.furnace.util.Predicate; import org.jboss.windup.config.AnalyzeKnownLibrariesOption; import org.jboss.windup.config.ConfigurationOption; +import org.jboss.windup.config.ExportZipOutputOption; import org.jboss.windup.config.RuleProvider; import org.jboss.windup.config.furnace.FurnaceHolder; import org.jboss.windup.exec.WindupProcessor; @@ -475,4 +476,13 @@ public boolean isExportingSummary() { return export == null ? false : export; } + public WindupConfiguration setExportingZipReport(boolean export) { + setOptionValue(ExportZipOutputOption.NAME, export); + return this; + } + public boolean isExportingZipReport() { + Boolean export = getOptionValue(ExportZipOutputOption.NAME); + return export != null && export; + } + } diff --git a/exec/impl/src/main/java/org/jboss/windup/exec/WindupProcessorImpl.java b/exec/impl/src/main/java/org/jboss/windup/exec/WindupProcessorImpl.java index 0466fbd7f2..821e2d99af 100644 --- a/exec/impl/src/main/java/org/jboss/windup/exec/WindupProcessorImpl.java +++ b/exec/impl/src/main/java/org/jboss/windup/exec/WindupProcessorImpl.java @@ -12,6 +12,7 @@ import org.jboss.forge.furnace.util.Assert; import org.jboss.forge.furnace.util.Predicate; import org.jboss.windup.config.DefaultEvaluationContext; +import org.jboss.windup.config.ExportZipOutputOption; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.KeepWorkDirsOption; import org.jboss.windup.config.LegacyReportsRenderingOption; @@ -197,6 +198,7 @@ private WindupConfigurationModel setupWindupConfigurationModel(WindupConfigurati configurationModel.setExportingCSV(configuration.isExportingCSV()); configurationModel.setExportingSummary(configuration.isExportingSummary()); configurationModel.setKeepWorkDirectories(configuration.getOptionValue(KeepWorkDirsOption.NAME)); + configurationModel.setExportingZipReport(configuration.isExportingZipReport()); addUserRulesDirsToConfig(configuration, graphContext, configurationModel); addUserLabelsDirsToConfig(configuration, graphContext, configurationModel); diff --git a/graph/api/src/main/java/org/jboss/windup/graph/model/WindupConfigurationModel.java b/graph/api/src/main/java/org/jboss/windup/graph/model/WindupConfigurationModel.java index ffb77e96d6..0718db21b7 100644 --- a/graph/api/src/main/java/org/jboss/windup/graph/model/WindupConfigurationModel.java +++ b/graph/api/src/main/java/org/jboss/windup/graph/model/WindupConfigurationModel.java @@ -30,6 +30,7 @@ public interface WindupConfigurationModel extends WindupVertexFrame { String SKIP_SOURCE_CODE_REPORTS_RENDERING = "skipSourceCodeReports"; String ANALYZE_KNOWN_LIBRARIES = "analyzeKnownLibraries"; String LEGACY_REPORTS = "legacyReports"; + String EXPORT_ZIP_REPORT = "exportZipReport"; /** * The input path to scan @@ -202,4 +203,16 @@ public interface WindupConfigurationModel extends WindupVertexFrame { */ @Property(SUMMARY_MODE) void setExportingSummary(boolean summary); + + /** + * Indicates whether or not to export ZIP file + */ + @Property(EXPORT_ZIP_REPORT) + boolean isExportingZipReport(); + + /** + * Indicates whether or not to export ZIP file + */ + @Property(EXPORT_ZIP_REPORT) + void setExportingZipReport(boolean summary); } diff --git a/reporting/impl/src/main/java/org/jboss/windup/reporting/export/ExportZipReportRuleProvider.java b/reporting/impl/src/main/java/org/jboss/windup/reporting/export/ExportZipReportRuleProvider.java new file mode 100644 index 0000000000..0bbbd0e766 --- /dev/null +++ b/reporting/impl/src/main/java/org/jboss/windup/reporting/export/ExportZipReportRuleProvider.java @@ -0,0 +1,61 @@ +package org.jboss.windup.reporting.export; + +import java.io.IOException; +import java.util.List; + +import org.jboss.windup.config.AbstractRuleProvider; +import org.jboss.windup.config.GraphRewrite; +import org.jboss.windup.config.loader.RuleLoaderContext; +import org.jboss.windup.config.metadata.RuleMetadata; +import org.jboss.windup.config.operation.GraphOperation; +import org.jboss.windup.config.phase.PostFinalizePhase; +import org.jboss.windup.config.query.WindupConfigurationQuery; +import org.jboss.windup.graph.GraphContextFactory; +import org.jboss.windup.graph.model.WindupConfigurationModel; +import org.jboss.windup.graph.model.resource.FileModel; +import org.jboss.windup.graph.service.WindupConfigurationService; +import org.jboss.windup.util.ZipUtil; +import org.jboss.windup.util.exception.WindupException; +import org.ocpsoft.rewrite.config.Configuration; +import org.ocpsoft.rewrite.config.ConfigurationBuilder; +import org.ocpsoft.rewrite.context.EvaluationContext; + +/** + * Creates a ZIP file containing all the files in the output path + * + * @author Marco Rizzi + */ +@RuleMetadata( + afterIDs = "DeleteWorkDirsAtTheEndRuleProvider", + description = "Creates a ZIP file containing all the files in the output path." + + " Use --" + WindupConfigurationModel.EXPORT_ZIP_REPORT + " to enable it.", + phase = PostFinalizePhase.class +) +public class ExportZipReportRuleProvider extends AbstractRuleProvider { + public static final String ZIP_REPORTS_NAME = "reports.zip"; + @Override + public Configuration getConfiguration(RuleLoaderContext ruleLoaderContext) { + + return ConfigurationBuilder.begin() + .addRule() + .when(WindupConfigurationQuery.hasOption(WindupConfigurationModel.EXPORT_ZIP_REPORT, true).as("discard")) + .perform( + new GraphOperation() { + public void perform(GraphRewrite event, EvaluationContext context) { + final WindupConfigurationModel windupConfiguration = WindupConfigurationService.getConfigurationModel(event.getGraphContext()); + try { + final FileModel outputFolderToZip = windupConfiguration.getOutputPath(); + ZipUtil.zipFolder(outputFolderToZip.asFile().toPath(), outputFolderToZip.getFilePath(), ZIP_REPORTS_NAME, List.of(GraphContextFactory.DEFAULT_GRAPH_SUBDIRECTORY)); + } catch (IOException e) { + throw new WindupException(e); + } + } + + public String toString() { + return "Create a ZIP file to collect the reports"; + } + } + ); + } + +} diff --git a/reporting/tests/src/test/java/org/jboss/windup/reporting/ZipExportingTest.java b/reporting/tests/src/test/java/org/jboss/windup/reporting/ZipExportingTest.java new file mode 100644 index 0000000000..fe965b3769 --- /dev/null +++ b/reporting/tests/src/test/java/org/jboss/windup/reporting/ZipExportingTest.java @@ -0,0 +1,135 @@ +package org.jboss.windup.reporting; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.inject.Inject; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.forge.arquillian.AddonDependencies; +import org.jboss.forge.arquillian.AddonDependency; +import org.jboss.forge.arquillian.archive.AddonArchive; +import org.jboss.forge.furnace.util.Predicate; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.windup.config.LegacyReportsRenderingOption; +import org.jboss.windup.config.RuleProvider; +import org.jboss.windup.config.SkipReportsRenderingOption; +import org.jboss.windup.engine.predicates.RuleProviderWithDependenciesPredicate; +import org.jboss.windup.exec.WindupProcessor; +import org.jboss.windup.exec.configuration.WindupConfiguration; +import org.jboss.windup.graph.GraphContext; +import org.jboss.windup.graph.GraphContextFactory; +import org.jboss.windup.reporting.export.ExportZipReportRuleProvider; +import org.jboss.windup.rules.apps.java.config.ScanPackagesOption; +import org.jboss.windup.rules.apps.java.config.SourceModeOption; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class ZipExportingTest { + + @Deployment + @AddonDependencies({ + @AddonDependency(name = "org.jboss.windup.config:windup-config"), + @AddonDependency(name = "org.jboss.windup.graph:windup-graph"), + @AddonDependency(name = "org.jboss.windup.reporting:windup-reporting"), + @AddonDependency(name = "org.jboss.windup.exec:windup-exec"), + @AddonDependency(name = "org.jboss.windup.rules.apps:windup-rules-java"), + @AddonDependency(name = "org.jboss.windup.utils:windup-utils"), + @AddonDependency(name = "org.jboss.forge.furnace.container:cdi") + }) + public static AddonArchive getDeployment() { + return ShrinkWrap.create(AddonArchive.class).addBeansXML(); + } + + @Inject + private GraphContextFactory factory; + + @Inject + private WindupProcessor processor; + + @Test + public void testExportZipReport() throws Exception { + zipReportTest(true, false, false); + } + + @Test + public void testExportZipReportLegacyReports() throws Exception { + zipReportTest(true, true, false); + } + + @Test + public void testExportZipReportSkipReports() throws Exception { + zipReportTest(true, false, true); + } + + @Test + public void testNotExportZipReport() throws Exception { + zipReportTest(false, false, false); + } + + private void zipReportTest(boolean exportZipReports, boolean legacyReports, boolean skipReports) throws Exception { + final Path outputPath = Paths.get(FileUtils.getTempDirectory().toString(), + "windup_" + RandomStringUtils.randomAlphanumeric(6)); + + outputPath.toFile().mkdirs(); + try (GraphContext context = factory.create(true)) { + String inputPath = "src/test/resources"; + Predicate predicate = new RuleProviderWithDependenciesPredicate(ExportZipReportRuleProvider.class); + WindupConfiguration configuration = new WindupConfiguration() + .setGraphContext(context) + .setRuleProviderFilter(predicate) + .addInputPath(Paths.get(inputPath)) + .setOutputDirectory(outputPath) + .setOptionValue(ScanPackagesOption.NAME, Collections.singletonList("")) + .setOptionValue(SourceModeOption.NAME, true); + if (exportZipReports) { + configuration.setExportingZipReport(true); + configuration.setOptionValue(LegacyReportsRenderingOption.NAME, legacyReports); + if (skipReports) configuration.setOptionValue(SkipReportsRenderingOption.NAME, true); + } + processor.execute(configuration); + final File reportsZipFile = new File(outputPath + "/reports.zip"); + Assert.assertEquals(exportZipReports, reportsZipFile.exists()); + if (exportZipReports) { + try (ZipFile reportsZip = new ZipFile(reportsZipFile); + Stream outputFiles = Files.walk(outputPath.toAbsolutePath())){ + final Set zipContent = reportsZip.stream() + .map(ZipEntry::getName) + .sorted() + .collect(Collectors.toCollection(LinkedHashSet::new)); + Assert.assertTrue( + "Zip file content is different from the output folder content", + outputFiles + // reports.zip file itself and root folder are not part of the ZIP file + .filter(file -> !file.endsWith("reports.zip") && !outputPath.equals(file)) + .map(path -> { + final Path relativePath = outputPath.relativize(path); + // in the ZipFile, the directories end with "/" but they don't in the Files.walk + if (Files.isDirectory(path)) { + return relativePath + File.separator; + } + else { + return relativePath.toString(); + } + }) + // check every file in the output path is contained in the ZIP file + .allMatch(zipContent::contains)); + } + } + } + } +} diff --git a/utils/src/main/java/org/jboss/windup/util/ZipUtil.java b/utils/src/main/java/org/jboss/windup/util/ZipUtil.java index 23737545ee..5e14aa88fd 100644 --- a/utils/src/main/java/org/jboss/windup/util/ZipUtil.java +++ b/utils/src/main/java/org/jboss/windup/util/ZipUtil.java @@ -5,12 +5,24 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.nio.file.CopyOption; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Logger; import java.util.zip.ZipEntry; @@ -32,6 +44,12 @@ public class ZipUtil { private static Set supportedExtensions; + private static final CopyOption[] COPY_OPTIONS = { + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES, + LinkOption.NOFOLLOW_LINKS + }; + /** * Unzip a classpath resource using the given {@link Class} as the resource root path. */ @@ -166,4 +184,40 @@ private static ZipEntry getNextEntry(ZipInputStream zis) throws IOException { } } + public static void zipFolder(Path source, String zipOutputPath, String zipOutputName, List pathPrefixesToExclude) throws IOException { + + final URI uri = URI.create("jar:file:" + zipOutputPath + File.separator + zipOutputName); + + Files.walkFileTree(source, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { + final Path targetFile = source.relativize(file); + if (attributes.isSymbolicLink() || file.endsWith(zipOutputName) || pathPrefixesToExclude.stream().anyMatch(targetFile::startsWith)) { + return FileVisitResult.CONTINUE; + } + + final Map env = new HashMap<>(); + env.put("create", "true"); + + try (final FileSystem zip = FileSystems.newFileSystem(uri, env)) { + final Path pathInZipfile = zip.getPath(targetFile.toString()); + if (pathInZipfile.getParent() != null) { + Files.createDirectories(pathInZipfile.getParent()); + } + Files.copy(file, pathInZipfile, COPY_OPTIONS); + } catch (IOException e) { + throw new WindupException(e); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + final String message = String.format("Unable to zip : %s%n%s%n", file, exc); + log.warning(message); + System.err.println(message); + return FileVisitResult.CONTINUE; + } + }); + } }