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