/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.itest.springboot.arquillian; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.Iterator; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ArchivePath; import org.jboss.shrinkwrap.api.Node; import org.jboss.shrinkwrap.api.exporter.ArchiveExportException; import org.jboss.shrinkwrap.impl.base.io.IOUtil; import org.jboss.shrinkwrap.impl.base.path.PathUtil; /** * A spring-boot compatible on-demand input stream. * It does not compress jar entries for spring-boot nested jar structure compatibility. */ public class SpringBootZipOnDemandInputStream extends InputStream { /** * Created by abstract method. */ protected ZipOutputStream outputStream; /** * Iterator over nodes contained in base archive. */ private final Iterator<Node> nodesIterator; /** * Base for outputStream. */ private final ByteArrayOutputStream bufferedOutputStream = new ByteArrayOutputStream(); /** * Stream of currently processed Node. */ private InputStream currentNodeStream; /** * Stream to the buffer. */ private ByteArrayInputStream bufferInputStream; /** * If output stream was closed - we should finish. */ private boolean outputStreamClosed; /** * Currently processed archive path - for displaying exception. */ private ArchivePath currentPath; /** * Creates stream directly from archive. * * @param archive */ public SpringBootZipOnDemandInputStream(final Archive<?> archive) { final Collection<Node> nodes = archive.getContent().values(); this.nodesIterator = nodes.iterator(); } @Override public int read() throws IOException { if (outputStream == null && !outputStreamClosed) { // first run outputStream = createOutputStream(bufferedOutputStream); } int value = bufferInputStream != null ? bufferInputStream.read() : -1; if (value == -1) { if (currentNodeStream != null) { // current node was not processed completely try { doCopy(); bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray()); bufferedOutputStream.reset(); return this.read(); } catch (final Throwable t) { throw new ArchiveExportException("Failed to write asset to output: " + currentPath.get(), t); } } else if (nodesIterator.hasNext()) { // current node was processed completely, process next one final Node currentNode = nodesIterator.next(); currentPath = currentNode.getPath(); final String pathName = PathUtil.optionallyRemovePrecedingSlash(currentPath.get()); final boolean isDirectory = currentNode.getAsset() == null; String resolvedPath = pathName; if (isDirectory) { resolvedPath = PathUtil.optionallyAppendSlash(resolvedPath); startAsset(resolvedPath, 0L, 0L); endAsset(); } else { try { byte[] content = IOUtil.asByteArray(currentNode.getAsset().openStream()); long size = content.length; CRC32 crc = new CRC32(); crc.update(content); long crc32Value = crc.getValue(); startAsset(resolvedPath, size, crc32Value); currentNodeStream = new ByteArrayInputStream(content); doCopy(); } catch (final Throwable t) { throw new ArchiveExportException("Failed to write asset to output: " + currentPath.get(), t); } bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray()); bufferedOutputStream.reset(); } } else { // each node was processed if (!outputStreamClosed) { outputStream.close(); outputStreamClosed = true; // output closed, now process what was saved on close bufferInputStream = new ByteArrayInputStream(bufferedOutputStream.toByteArray()); bufferedOutputStream.close(); currentNodeStream = null; outputStream = null; return this.read(); } // everything was read, end return -1; } // chosen new node or new data in buffer - read again return this.read(); } return value; } /** * Performs copy operation between currentNodeStream and outputStream using buffer length. * * @throws IOException */ private void doCopy() throws IOException { IOUtil.copy(currentNodeStream, outputStream); currentNodeStream.close(); currentNodeStream = null; endAsset(); } /** * Start entry in stream. * * @param path * @throws IOException */ private void startAsset(final String path, long size, long crc32) throws IOException { putNextEntry(outputStream, path, size, crc32); } /** * Close entry in stream. * * @throws IOException */ private void endAsset() throws IOException { closeEntry(outputStream); } protected ZipOutputStream createOutputStream(final OutputStream outputStream) { ZipOutputStream stream = new ZipOutputStream(outputStream); stream.setMethod(ZipEntry.STORED); stream.setLevel(Deflater.NO_COMPRESSION); return stream; } protected void closeEntry(final ZipOutputStream outputStream) throws IOException { outputStream.closeEntry(); } protected void putNextEntry(final ZipOutputStream outputStream, final String context, long size, long crc32) throws IOException { ZipEntry entry = new ZipEntry(context); entry.setMethod(ZipEntry.STORED); entry.setSize(size); entry.setCrc(crc32); outputStream.putNextEntry(entry); } }