package org.jboss.windup.rules.apps.java.scan.operation; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.logging.Logger; import org.apache.commons.io.filefilter.TrueFileFilter; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.operation.iteration.AbstractIterationOperation; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.model.ArchiveModel; import org.jboss.windup.graph.model.DuplicateArchiveModel; import org.jboss.windup.graph.model.WindupConfigurationModel; import org.jboss.windup.graph.model.resource.FileModel; import org.jboss.windup.graph.model.resource.IgnoredFileModel; import org.jboss.windup.graph.service.FileService; import org.jboss.windup.graph.service.GraphService; import org.jboss.windup.graph.service.WindupConfigurationService; import org.jboss.windup.reporting.service.ClassificationService; import org.jboss.windup.rules.apps.java.archives.model.IdentifiedArchiveModel; import org.jboss.windup.rules.apps.java.service.WindupJavaConfigurationService; import org.jboss.windup.util.Logging; import org.jboss.windup.util.ZipUtil; import org.jboss.windup.util.exception.WindupException; import org.ocpsoft.rewrite.context.EvaluationContext; /** * This Operation unzips the file pointed to by payload (ArchiveModel) to a temporary folder. */ public class UnzipArchiveToOutputFolder extends AbstractIterationOperation<ArchiveModel> { private static final String MALFORMED_ARCHIVE = "Malformed archive"; private static final String ARCHIVES = "archives"; private static final Logger LOG = Logging.get(UnzipArchiveToOutputFolder.class); public UnzipArchiveToOutputFolder() { super(); } public static UnzipArchiveToOutputFolder unzip() { return new UnzipArchiveToOutputFolder(); } @Override public void perform(GraphRewrite event, EvaluationContext context, ArchiveModel payload) { LOG.info("Unzipping archive: " + payload.toPrettyString()); File zipFile = payload.asFile(); if (zipFile == null || !zipFile.isFile()) { throw new WindupException("Input path doesn't point to a file: " + (zipFile == null ? "null" : zipFile.getAbsolutePath())); } final GraphContext graphContext = event.getGraphContext(); // Create a folder for all archive contents. Path unzippedArchiveDir = getArchivesDirLocation(graphContext); ensureDirIsCreated(unzippedArchiveDir); unzipToTempDirectory(event, context, unzippedArchiveDir, zipFile, payload, false); } public static Path getArchivesDirLocation(final GraphContext graphContext) { WindupConfigurationModel cfg = WindupConfigurationService.getConfigurationModel(graphContext); String windupOutputFolder = cfg.getOutputPath().getFilePath(); return Paths.get(windupOutputFolder, ARCHIVES); } private void unzipToTempDirectory(final GraphRewrite event, EvaluationContext context, final Path tempFolder, final File inputZipFile, final ArchiveModel archiveModel, boolean subArchivesOnly) { final FileService fileService = new FileService(event.getGraphContext()); // Setup a temp folder for the archive String appArchiveName = archiveModel.getArchiveName(); if (null == appArchiveName) throw new IllegalStateException("Archive model doesn't have an archiveName: " + archiveModel.getFilePath()); final Path appArchiveFolder = getNonexistentDirForAppArchive(tempFolder, appArchiveName); ensureDirIsCreated(appArchiveFolder); // Unzip to the temp folder. LOG.info("Unzipping " + inputZipFile.getPath() + " to " + appArchiveFolder.toString()); try { ZipUtil.unzipToFolder(inputZipFile, appArchiveFolder.toFile()); } catch (Throwable e) { // only mark the canonical archive, as we only need to add this classification once ArchiveModel canonicalArchive = archiveModel; if (canonicalArchive instanceof DuplicateArchiveModel) canonicalArchive = ((DuplicateArchiveModel)canonicalArchive).getCanonicalArchive(); ClassificationService classificationService = new ClassificationService(event.getGraphContext()); classificationService.attachClassification(event, context, canonicalArchive, MALFORMED_ARCHIVE, "Cannot unzip the file"); archiveModel.setParseError("Cannot unzip the file: " + e.getMessage()); LOG.warning("Cannot unzip the file " + inputZipFile.getPath() + " to " + appArchiveFolder.toString() + ". The ArchiveModel was classified as malformed."); return; } // mark the path to the archive archiveModel.setUnzippedDirectory(appArchiveFolder.toString()); // add all unzipped files, and make sure their parent archive is set recurseAndAddFiles(event, context, tempFolder, fileService, archiveModel, archiveModel, subArchivesOnly); } /** * Recurses the given folder and adds references to these files to the graph as FileModels. * * We don't set the parent file model in the case of the initial children, as the direct parent is really the archive itself. For example for file * "root.zip/pom.xml" - the parent for pom.xml is root.zip, not the directory temporary directory that happens to hold it. */ private void recurseAndAddFiles(GraphRewrite event, EvaluationContext context, Path tempFolder, FileService fileService, ArchiveModel archiveModel, FileModel parentFileModel, boolean subArchivesOnly) { int numberAdded = 0; FileFilter filter = TrueFileFilter.TRUE; if (archiveModel instanceof IdentifiedArchiveModel) { filter = new IdentifiedArchiveFileFilter(archiveModel); } File fileReference; if (parentFileModel instanceof ArchiveModel) fileReference = new File(((ArchiveModel) parentFileModel).getUnzippedDirectory()); else fileReference = parentFileModel.asFile(); WindupJavaConfigurationService windupJavaConfigurationService = new WindupJavaConfigurationService(event.getGraphContext()); File[] subFiles = fileReference.listFiles(); if (subFiles == null) return; for (File subFile : subFiles) { if (!filter.accept(subFile)) continue; if (subArchivesOnly && !ZipUtil.endsWithZipExtension(subFile.getAbsolutePath())) continue; FileModel subFileModel = fileService.createByFilePath(parentFileModel, subFile.getAbsolutePath()); // check if this file should be ignored if (checkIfIgnored(event, subFileModel, windupJavaConfigurationService.getIgnoredFileRegexes())) continue; numberAdded++; if (numberAdded % 250 == 0) event.getGraphContext().getGraph().getBaseGraph().commit(); if (subFile.isFile() && ZipUtil.endsWithZipExtension(subFileModel.getFilePath())) { File newZipFile = subFileModel.asFile(); ArchiveModel newArchiveModel = GraphService.addTypeToModel(event.getGraphContext(), subFileModel, ArchiveModel.class); newArchiveModel.setParentArchive(archiveModel); newArchiveModel.setArchiveName(newZipFile.getName()); /* * New archive must be reloaded in case the archive should be ignored */ newArchiveModel = GraphService.refresh(event.getGraphContext(), newArchiveModel); ArchiveModel canonicalArchiveModel = null; for (FileModel otherMatches : fileService.findAllByProperty(FileModel.SHA1_HASH, newArchiveModel.getSHA1Hash())) { if (otherMatches instanceof ArchiveModel && !otherMatches.equals(newArchiveModel) && !(otherMatches instanceof DuplicateArchiveModel)) { canonicalArchiveModel = (ArchiveModel)otherMatches; break; } } if (canonicalArchiveModel != null) { // handle as duplicate DuplicateArchiveModel duplicateArchive = GraphService.addTypeToModel(event.getGraphContext(), newArchiveModel, DuplicateArchiveModel.class); duplicateArchive.setCanonicalArchive(canonicalArchiveModel); // create dupes for child archives unzipToTempDirectory(event, context, tempFolder, newZipFile, duplicateArchive, true); } else { unzipToTempDirectory(event, context, tempFolder, newZipFile, newArchiveModel, false); } } else if (subFile.isDirectory()) { recurseAndAddFiles(event, context, tempFolder, fileService, archiveModel, subFileModel, false); } } } /** * Checks if the {@link FileModel#getFilePath()} + {@link FileModel#getFileName()} is ignored by any of the specified regular expressions. */ private boolean checkIfIgnored(final GraphRewrite event, FileModel file, List<String> patterns) { boolean ignored = false; if (patterns != null && !patterns.isEmpty()) { for (String pattern : patterns) { if (file.getFilePath().matches(pattern)) { IgnoredFileModel ignoredFileModel = GraphService.addTypeToModel(event.getGraphContext(), file, IgnoredFileModel.class); ignoredFileModel.setIgnoredRegex(pattern); LOG.info("File/Directory placed in " + file.getFilePath() + " was ignored, because matched [" + pattern + "]."); ignored = true; break; } } } return ignored; } private static Path getNonexistentDirForAppArchive(Path tempFolder, String appArchiveName) { Path appArchiveFolder = Paths.get(tempFolder.toString(), appArchiveName); int fileIdx = 1; // If it is already created, try another folder name. while (Files.exists(appArchiveFolder)) { appArchiveFolder = Paths.get(tempFolder.toString(), appArchiveName + "." + fileIdx); fileIdx++; } return appArchiveFolder; } private static void ensureDirIsCreated(Path windupTempUnzippedArchiveFolder) throws WindupException { if (!Files.isDirectory(windupTempUnzippedArchiveFolder)) { try { Files.createDirectories(windupTempUnzippedArchiveFolder); } catch (IOException e) { throw new WindupException("Failed to create temporary folder for archives: " + windupTempUnzippedArchiveFolder + "\n\tdue to: " + e.getMessage(), e); } } } @Override public String toString() { return UnzipArchiveToOutputFolder.class.getSimpleName(); } }