/* * Sonatype Nexus (TM) Open Source Version * Copyright (c) 2018-present Sonatype, Inc. * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions. * * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. * * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the * Eclipse Foundation. All other trademarks are the property of their respective owners. */ package org.sonatype.nexus.repository.composer.internal; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; import javax.inject.Named; import javax.inject.Singleton; import org.sonatype.goodies.common.ComponentSupport; import org.sonatype.nexus.blobstore.api.Blob; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveInputStream; import org.apache.commons.compress.archivers.ArchiveStreamFactory; /** * Utility class for extracting the contents of a package's {@code composer.json} file and returning it as a map. */ @Named @Singleton public class ComposerJsonExtractor extends ComponentSupport { private final TypeReference<Map<String, Object>> typeReference = new TypeReference<Map<String, Object>>() { }; private final ObjectMapper mapper = new ObjectMapper(); private final ArchiveStreamFactory archiveStreamFactory = new ArchiveStreamFactory(); /** * Extracts the contents for the first matching {@code composer.json} file (of which there should only be one) as a * map representing the parsed JSON content. If no such file is found then an empty map is returned. */ public Map<String, Object> extractFromZip(final Blob blob) throws IOException { try (InputStream is = blob.getInputStream()) { try (ArchiveInputStream ais = archiveStreamFactory.createArchiveInputStream(ArchiveStreamFactory.ZIP, is)) { ArchiveEntry entry = ais.getNextEntry(); while (entry != null) { Map<String, Object> contents = processEntry(ais, entry); if (!contents.isEmpty()) { return contents; } entry = ais.getNextEntry(); } } return Collections.emptyMap(); } catch (ArchiveException e) { throw new IOException("Error reading from archive", e); } } /** * Processes a single entry in the archive. If the entry is the composer.json then the attributes will be extracted. * If not, the entry is skipped. */ private Map<String, Object> processEntry(final ArchiveInputStream stream, final ArchiveEntry entry) throws IOException { if (isComposerJsonFilename(entry.getName())) { return mapper.readValue(stream, typeReference); } return Collections.emptyMap(); } /** * Returns a boolean indicating if the associated file path (from an archive file) represents the {@code * composer.json} file. */ private boolean isComposerJsonFilename(final String entryName) { int filenameIndex = entryName.indexOf("/composer.json"); int separatorIndex = entryName.indexOf("/"); return entryName.equals("composer.json") || (filenameIndex >= 0 && filenameIndex == separatorIndex); } }