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