FELIX-4486: fix possible thread leakage:
- replaced the ExplodingOutputtingInputStream with a much simpler
ContentCopyingJarInputStream that levarages the JarInputStream to copy
entries while they are read;
- added some test cases to verify the new implementation.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1587613 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java
new file mode 100644
index 0000000..bbaec0e
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java
@@ -0,0 +1,187 @@
+/*
+ * 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 java.io.Closeable;
+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 the
+ * common 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);
+
+ 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();
+ super.close();
+ }
+
+ 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;
+ }
+
+ private void closeSilently(Closeable resource)
+ {
+ try
+ {
+ if (resource != null)
+ {
+ resource.close();
+ }
+ }
+ catch (IOException e)
+ {
+ // Ignore, not much we can do about this...
+ }
+ }
+
+ /**
+ * 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));
+ }
+}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
index aeb2624..4a3b497 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -131,8 +131,8 @@
return m_packageAdmin;
}
- public DeploymentPackage installDeploymentPackage(InputStream input) throws DeploymentException {
- if (input == null) {
+ public DeploymentPackage installDeploymentPackage(InputStream sourceInput) throws DeploymentException {
+ if (sourceInput == null) {
throw new IllegalArgumentException("Inputstream may not be null");
}
@@ -163,7 +163,6 @@
tempIndex = new File(tempPackage, PACKAGEINDEX_FILE);
tempContents = new File(tempPackage, PACKAGECONTENTS_DIR);
tempContents.mkdirs();
- input = new ExplodingOutputtingInputStream(input, tempIndex, tempContents);
}
catch (IOException e) {
m_log.log(LogService.LOG_ERROR, "Error writing package to disk", e);
@@ -171,7 +170,7 @@
}
try {
- jarInput = new JarInputStream(input);
+ jarInput = new ContentCopyingJarInputStream(sourceInput, tempIndex, tempContents);
if (jarInput.getManifest() == null) {
m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid deployment package: missing manifest!");
@@ -206,31 +205,12 @@
verifySourcePackage(source);
}
- // To keep track whether or not we're masking an exception during the close of the input stream...
- boolean installFailed = false;
-
try {
m_session = new DeploymentSessionImpl(source, target, createInstallCommandChain(), this);
m_session.call(false /* ignoreExceptions */);
}
catch (DeploymentException de) {
- installFailed = true;
throw de;
- } finally {
- try {
- // make sure we've read until the end-of-stream, so the explodingoutput-wrapper can process all bytes
- Utils.readUntilEndOfStream(input);
-
- // note that calling close on this stream will wait until asynchronous processing is done
- input.close();
- }
- catch (IOException e) {
- m_log.log(LogService.LOG_ERROR, "Could not close stream properly", e);
- // Do not mask out any originally thrown exceptions...
- if (!installFailed) {
- throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not close stream properly", e);
- }
- }
}
String dpInstallBaseDirectory = PACKAGE_DIR + File.separator + dpSymbolicName;
@@ -277,6 +257,7 @@
succeeded = false;
}
}
+
sendCompleteEvent(source, target, succeeded);
m_semaphore.release();
}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java
deleted file mode 100644
index 001159e..0000000
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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 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.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.io.PrintWriter;
-import java.util.zip.GZIPOutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * This class will write all entries encountered in an input stream to disk. An index of files written to disk is kept in an index file in the
- * order they were encountered. Each file is compressed using GZIP. All the work is done on a separate thread.
- */
-class ExplodingOutputtingInputStream extends OutputtingInputStream {
-
- static class ReaderThread extends Thread {
-
- private final File m_contentDir;
- private final File m_indexFile;
- private final PipedInputStream m_input;
-
- private volatile Exception m_exception;
-
- public ReaderThread(PipedOutputStream output, File index, File root) throws IOException {
- super("Apache Felix DeploymentAdmin - ExplodingOutputtingInputStream");
-
- m_contentDir = root;
- m_indexFile = index;
- m_input = new PipedInputStream(output);
- }
-
- public void run() {
- byte[] buffer = new byte[4096];
-
- ZipInputStream input = null;
- PrintWriter writer = null;
- try {
- input = new ZipInputStream(m_input);
- writer = new PrintWriter(new FileWriter(m_indexFile));
-
- for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
- File current = new File(m_contentDir, entry.getName());
- if (entry.isDirectory()) {
- current.mkdirs();
- }
- else {
- writer.println(entry.getName());
- File parent = current.getParentFile();
- if (parent != null) {
- parent.mkdirs();
- }
- OutputStream output = null;
- try {
- output = new GZIPOutputStream(new FileOutputStream(current));
- for (int i = input.read(buffer); i > -1; i = input.read(buffer)) {
- output.write(buffer, 0, i);
- }
- }
- finally {
- output.close();
- }
- }
- input.closeEntry();
- writer.flush();
- }
- }
- catch (IOException ex) {
- pushException(ex);
- }
- finally {
- if (writer != null) {
- writer.close();
- }
- }
-
- try {
- Utils.readUntilEndOfStream(m_input);
- }
- catch (IOException e) {
- pushException(e);
- }
- finally {
- if (input != null) {
- try {
- input.close();
- }
- catch (IOException e) {
- pushException(e);
- }
- }
- }
- }
-
- private void pushException(Exception e) {
- Exception e2 = new Exception(e.getMessage());
- e2.setStackTrace(e.getStackTrace());
- if (m_exception != null) {
- e2.initCause(m_exception);
- }
- m_exception = e2;
- }
- }
-
- private final ReaderThread m_task;
-
- /**
- * Creates an instance of this class.
- *
- * @param inputStream The input stream that will be written to disk as individual entries as it's read.
- * @param indexFile File to be used to write the index of all encountered files.
- * @param contentDir File to be used as the directory to hold all files encountered in the stream.
- * @throws IOException If a problem occurs reading the stream resources.
- */
- public ExplodingOutputtingInputStream(InputStream inputStream, File indexFile, File contentDir) throws IOException {
- this(inputStream, new PipedOutputStream(), indexFile, contentDir);
- }
-
- private ExplodingOutputtingInputStream(InputStream inputStream, PipedOutputStream output, File index, File root) throws IOException {
- super(inputStream, output);
- m_task = new ReaderThread(output, index, root);
- m_task.start();
- }
-
- public void close() throws IOException {
- try {
- super.close();
-
- Exception exception = m_task.m_exception;
- if (exception != null) {
- throw (IOException) new IOException("Exception while processing the stream in the background: " + exception.getMessage()).initCause(exception);
- }
- }
- finally {
- waitFor();
- }
- }
-
- private void waitFor() {
- try {
- m_task.join();
- }
- catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.java
deleted file mode 100644
index ac8fa36..0000000
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * This extension of the <code>InputStream</code> writes every byte that is read to an
- * <code>OutputStream</code> of choice. The outputstream is closed automatically when
- * the end of the inputstream is reached.
- */
-public class OutputtingInputStream extends InputStream {
-
- private final InputStream m_inputStream;
- private final OutputStream m_outputStream;
-
- /**
- * Creates an instance of the <code>OutputtingInputStream</code>.
- *
- * @param inputStream The inputstream from which bytes will be read.
- * @param outputStream The outputstream to which every byte that is read should be outputted.
- */
- public OutputtingInputStream(InputStream inputStream, OutputStream outputStream) {
- super();
- m_inputStream = inputStream;
- m_outputStream = outputStream;
- }
-
- public int read() throws IOException {
- int i = m_inputStream.read();
- if (i != -1) {
- m_outputStream.write(i);
- }
- return i;
- }
-
- public int read(byte[] buffer) throws IOException {
- int i = m_inputStream.read(buffer);
- if (i != -1) {
- m_outputStream.write(buffer, 0, i);
- }
- return i;
- }
-
- public int read(byte[] buffer, int off, int len) throws IOException {
- int i = m_inputStream.read(buffer, off, len);
- if (i != -1) {
- m_outputStream.write(buffer, off, i);
- }
- return i;
- }
-
- public void close() throws IOException {
- try {
- m_inputStream.close();
- }
- finally {
- try {
- m_outputStream.close();
- }
- catch (Exception e) {
- // TODO: review the implications of this
- }
- }
- }
-}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
index 2627b3b..9279b50 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
@@ -33,12 +33,15 @@
import java.util.Iterator;
import java.util.List;
import java.util.jar.Attributes;
+import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.jar.Attributes.Name;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class Utils {
+ private static final String MANIFEST_NAME = JarFile.MANIFEST_NAME;
+
public static Manifest readManifest(File manifestFile) throws IOException {
InputStream is = null;
Manifest mf = null;
@@ -51,14 +54,6 @@
}
return mf;
}
-
- public static void readUntilEndOfStream(InputStream is) throws IOException {
- byte[] buffer = new byte[1024];
- int c = is.read(buffer);
- while (c != -1) {
- c = is.read(buffer);
- }
- }
public static boolean replace(File target, File source) {
return delete(target, true /* deleteRoot */) && rename(source, target);
@@ -153,7 +148,7 @@
for (Iterator i = result.iterator(); i.hasNext();) {
String targetFile = (String) i.next();
- if (!"META-INF/MANIFEST.MF".equals(targetFile) && !resultManifest.getEntries().containsKey(targetFile)) {
+ if (!MANIFEST_NAME.equals(targetFile) && !resultManifest.getEntries().containsKey(targetFile)) {
i.remove();
}
}
@@ -193,7 +188,7 @@
}
}
- GZIPOutputStream outputStream = new GZIPOutputStream(new FileOutputStream(new File(target, "META-INF/MANIFEST.MF")));
+ GZIPOutputStream outputStream = new GZIPOutputStream(new FileOutputStream(new File(target, MANIFEST_NAME)));
try {
resultManifest.write(outputStream);
} finally {
diff --git a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStreamTest.java b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStreamTest.java
new file mode 100644
index 0000000..e7cbccc
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStreamTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for {@link ContentCopyingJarInputStream}.
+ */
+public class ContentCopyingJarInputStreamTest extends TestCase
+{
+ private static final String MANIFEST_NAME = JarFile.MANIFEST_NAME;
+ private static final String INDEX_NAME = "META-INF/INDEX.LIST";
+
+ private File m_tempDir;
+ private File m_jarFile;
+
+ /**
+ * Tests that we can copy a simple {@link JarInputStream}.
+ */
+ public void testCopyEmptyJarOk() throws Exception
+ {
+ createEmptyJar();
+
+ assertJarContents(null);
+ }
+
+ /**
+ * Tests that we can copy a simple {@link JarInputStream}.
+ */
+ public void testCopyJarWithIndexAndWithManifestOk() throws Exception
+ {
+ Manifest man = createManifest();
+
+ createJar(man, true /* includeIndex */);
+
+ assertJarContents(man);
+ }
+
+ /**
+ * Tests that we can copy a {@link JarInputStream} even if it does not contains a manifest file.
+ */
+ public void testCopyJarWithIndexAndWithoutManifestOk() throws Exception
+ {
+ Manifest man = null;
+
+ createJar(man, true /* includeIndex */);
+
+ assertJarContents(man);
+ }
+
+ /**
+ * Tests that we can copy a simple {@link JarInputStream}.
+ */
+ public void testCopyJarWithoutIndexAndWithManifestOk() throws Exception
+ {
+ Manifest man = createManifest();
+
+ createJar(man, false /* includeIndex */);
+
+ assertJarContents(man);
+ }
+
+ /**
+ * Tests that we can copy a {@link JarInputStream} even if it does not contains a manifest file.
+ */
+ public void testCopyJarWithoutIndexAndWithoutManifestOk() throws Exception
+ {
+ Manifest man = null;
+
+ createJar(man, false /* includeIndex */);
+
+ assertJarContents(man);
+ }
+
+ protected void setUp() throws IOException
+ {
+ m_tempDir = createTempDir();
+ m_jarFile = new File(m_tempDir, "input.jar");
+ }
+
+ protected void tearDown()
+ {
+ Utils.delete(m_tempDir, true);
+ }
+
+ private void appendFiles(JarOutputStream jos, int count) throws IOException
+ {
+ int size = 1024;
+
+ for (int i = 0, j = 1; i < count; i++, j++)
+ {
+ JarEntry entry = new JarEntry("sub/" + j);
+ jos.putNextEntry(entry);
+ for (int k = 0; k < size; k++)
+ {
+ jos.write('0' + j);
+ }
+ jos.closeEntry();
+ }
+ }
+
+ private void assertJarContents(Manifest man) throws IOException
+ {
+ File indexFile = new File(m_tempDir, "index.txt");
+
+ FileInputStream fis = new FileInputStream(m_jarFile);
+ JarInputStream jis = new ContentCopyingJarInputStream(fis, indexFile, m_tempDir);
+
+ try
+ {
+ JarEntry entry;
+ while ((entry = jis.getNextJarEntry()) != null)
+ {
+ File f = new File(m_tempDir, entry.getName());
+
+ // Without reading the actual contents, the copy should already exist...
+ assertTrue(entry.getName() + " does not exist?!", f.exists());
+
+ int size = (INDEX_NAME.equals(entry.getName()) ? 33 : 1024);
+
+ byte[] input = new byte[size];
+ int read = jis.read(input);
+
+ assertEquals("Not all bytes were read: " + entry.getName(), size, read);
+
+ // Contents will only be completely written after closing the JAR entry itself...
+ jis.closeEntry();
+
+ verifyContents(f, input);
+ }
+
+ assertEquals("Manifest not as expected", man, jis.getManifest());
+ }
+ finally
+ {
+ jis.close();
+ }
+ }
+
+ private void createEmptyJar() throws IOException {
+ FileOutputStream fos = new FileOutputStream(m_jarFile);
+ JarOutputStream jos = new JarOutputStream(fos);
+ jos.close();
+ }
+
+ private void createJar(Manifest man, boolean includeIndex) throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream(m_jarFile);
+ JarOutputStream jos;
+
+ if (man == null || includeIndex)
+ {
+ jos = new JarOutputStream(fos);
+ }
+ else
+ {
+ jos = new JarOutputStream(fos, man);
+ }
+
+ if (includeIndex)
+ {
+ // Write the INDEX.LIST file as first entry...
+ jos.putNextEntry(new ZipEntry(INDEX_NAME));
+ jos.write(("JarIndex-Version: 1.0\n\n" + m_jarFile.getName() + "\n").getBytes());
+ jos.closeEntry();
+
+ if (man != null)
+ {
+ jos.putNextEntry(new ZipEntry(MANIFEST_NAME));
+ man.write(jos);
+ jos.closeEntry();
+ }
+ }
+
+ try
+ {
+ appendFiles(jos, 5);
+ }
+ finally
+ {
+ jos.close();
+ }
+ }
+
+ private Manifest createManifest()
+ {
+ Manifest mf = new Manifest();
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+ mf.getMainAttributes().putValue("Bundle-ManifestVersion", "2");
+ mf.getMainAttributes().putValue("Bundle-Version", "1.0.0");
+ mf.getMainAttributes().putValue("Bundle-SymbolicName", "com.foo.bar");
+ return mf;
+ }
+
+ private File createTempDir() throws IOException
+ {
+ File tmpFile = File.createTempFile("ccjis_test", null);
+ tmpFile.delete();
+ tmpFile.mkdir();
+ return tmpFile;
+ }
+
+ private void verifyContents(File file, byte[] expected) throws IOException
+ {
+ FileInputStream fis = new FileInputStream(file);
+ GZIPInputStream gis = new GZIPInputStream(fis);
+ try
+ {
+ byte[] b = new byte[expected.length];
+
+ int read = gis.read(b);
+ assertEquals(b.length, read);
+
+ for (int i = 0; i < expected.length; i++)
+ {
+ assertEquals(expected[i], b[i]);
+ }
+ }
+ finally
+ {
+ gis.close();
+ fis.close();
+ }
+ }
+}
diff --git a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStreamTest.java b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStreamTest.java
deleted file mode 100644
index bbc9ee7..0000000
--- a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStreamTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import junit.framework.TestCase;
-
-public class ExplodingOutputtingInputStreamTest extends TestCase {
- public void testStream() throws Exception {
- // fill up a stringbuffer with some test data
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < 1000; i++) {
- sb.append("DATAdataDATAdata");
- }
- String data = sb.toString();
-
- // create a temporary folder
- File tempDir = File.createTempFile("temp", "dir");
- tempDir.delete();
- tempDir.mkdirs();
- System.out.println("Dir: " + tempDir);
-
- // create a zip file with two entries in it
- File zipfile = new File(tempDir, "zipfile");
- ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfile));
- String dummy1 = "dummy";
- zos.putNextEntry(new ZipEntry(dummy1));
- zos.write(data.getBytes());
- zos.closeEntry();
- String dummy2 = "dummy2";
- zos.putNextEntry(new ZipEntry(dummy2));
- zos.write(data.getBytes());
- zos.closeEntry();
- zos.close();
-
- // create another temporary folder
- File dir = new File(tempDir, "dir");
- dir.mkdirs();
- File index = new File(tempDir, "list");
- ExplodingOutputtingInputStream stream = new ExplodingOutputtingInputStream(new FileInputStream(zipfile), index, dir);
- byte[] buffer = new byte[2];
- int read = stream.read(buffer);
- while (read != -1) {
- read = stream.read(buffer);
- }
- stream.close();
-
- // create references to the unpacked dummy files
- File d1 = new File(dir, dummy1);
- File d2 = new File(dir, dummy2);
-
- // cleanup
- zipfile.delete();
- index.delete();
- d1.delete();
- d2.delete();
- dir.delete();
- tempDir.delete();
- }
-
- public void testStreamReadWithJARStream() throws Exception {
- // fill up a stringbuffer with some test data
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < 1000; i++) {
- sb.append("DATAdataDATAdata");
- }
- String data = sb.toString();
-
- // create a temporary folder
- File tempDir = File.createTempFile("temp", "dir");
- tempDir.delete();
- tempDir.mkdirs();
- System.out.println("Dir: " + tempDir);
-
- // create a zip file with two entries in it
- File jarfile = new File(tempDir, "jarfile");
- JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarfile));
- String dummy1 = "dummy";
- jos.putNextEntry(new JarEntry(dummy1));
- jos.write(data.getBytes());
- jos.closeEntry();
- String dummy2 = "dummy2";
- jos.putNextEntry(new JarEntry(dummy2));
- jos.write(data.getBytes());
- jos.closeEntry();
- jos.close();
-
- // create another temporary folder
- File dir = new File(tempDir, "dir");
- dir.mkdirs();
- File index = new File(tempDir, "list");
- ExplodingOutputtingInputStream stream = new ExplodingOutputtingInputStream(new FileInputStream(jarfile), index, dir);
- JarInputStream jarInputStream = new JarInputStream(stream);
-
- JarEntry entry;
- while ((entry = jarInputStream.getNextJarEntry()) != null) {
- int size = 0;
- byte[] buffer = new byte[4096];
- for (int i = jarInputStream.read(buffer); i > -1; i = jarInputStream.read(buffer)) {
- size += i;
- }
- System.out.println("read JAR entry: " + entry + " of " + size + " bytes.");
- jarInputStream.closeEntry();
- }
- stream.close();
-
- // create references to the unpacked dummy files
- File d1 = new File(dir, dummy1);
- File d2 = new File(dir, dummy2);
-
- // cleanup
- jarfile.delete();
- index.delete();
- d1.delete();
- d2.delete();
- dir.delete();
- tempDir.delete();
- }
-}