content: /*
   * 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);
      }
  
  }
  