blob: 7dff1da06a0139ae91b21770b9587401e16ce40b [file] [log] [blame]
/*
* 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.felix.deploymentadmin;
import static org.apache.felix.deploymentadmin.Utils.closeSilently;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
/**
* Provides a custom {@link JarInputStream} that copies all entries read from the original {@link InputStream} to a
* given directory and index file. It does this by tracking thecommon usage of the {@link JarInputStream} API. For each
* entry that is read it streams all read bytes to a separate file compressing it on the fly. The caller does not notice
* anything, although it might be that the {@link #read(byte[], int, int)} is blocked for a little while during the
* writing of the file contents.
* <p>
* This implementation replaces the old <tt>ExplodingOutputtingInputStream</tt> that used
* at least two threads and was difficult to understand and maintain. See FELIX-4486.
* </p>
*/
class ContentCopyingJarInputStream extends JarInputStream {
private static final String MANIFEST_FILE = JarFile.MANIFEST_NAME;
private final File m_contentDir;
private PrintWriter m_indexFileWriter;
/** Used to copy the contents of the *next* entry. */
private OutputStream m_entryOS;
public ContentCopyingJarInputStream(InputStream in, File indexFile, File contentDir) throws IOException {
super(in, true /* verify */);
m_contentDir = contentDir;
m_indexFileWriter = new PrintWriter(new FileWriter(indexFile));
m_entryOS = null;
// the manifest of the JAR is already read by JarInputStream, so we need to write this one as well...
Manifest manifest = getManifest();
if (manifest != null) {
copyManifest(manifest);
}
}
public void close() throws IOException {
closeCopy();
closeIndex();
// Do NOT close our parent, as it is the original input stream which is not under our control...
}
public void closeEntry() throws IOException {
closeCopy();
super.closeEntry();
}
public ZipEntry getNextEntry() throws IOException {
closeCopy();
ZipEntry entry = super.getNextEntry();
if (entry != null) {
File current = new File(m_contentDir, entry.getName());
if (!entry.isDirectory()) {
addToIndex(entry.getName());
m_entryOS = createOutputStream(current);
}
}
return entry;
}
public int read(byte[] b, int off, int len) throws IOException {
int r = super.read(b, off, len);
if (m_entryOS != null) {
if (r > 0) {
m_entryOS.write(b, off, r);
}
else {
closeCopy();
}
}
return r;
}
private void addToIndex(String name) throws IOException {
m_indexFileWriter.println(name);
m_indexFileWriter.flush();
}
private void closeCopy() {
closeSilently(m_entryOS);
m_entryOS = null;
}
private void closeIndex() {
closeSilently(m_indexFileWriter);
m_indexFileWriter = null;
}
/**
* Creates a verbatim copy of the manifest, when it is read from the original JAR.
*/
private void copyManifest(Manifest manifest) throws IOException {
addToIndex(MANIFEST_FILE);
OutputStream os = createOutputStream(new File(m_contentDir, MANIFEST_FILE));
try {
manifest.write(os);
}
finally {
closeSilently(os);
}
}
private OutputStream createOutputStream(File file) throws IOException {
File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
if (!file.createNewFile()) {
throw new IOException("Attempt to overwrite file: " + file);
}
return new GZIPOutputStream(new FileOutputStream(file));
}
}