/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.jboss.shrinkwrap.impl.base.exporter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.exporter.ArchiveExportException;
import org.jboss.shrinkwrap.api.exporter.FileExistsException;
import org.jboss.shrinkwrap.api.exporter.StreamExporter;
import org.jboss.shrinkwrap.api.importer.StreamImporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.junit.Assert;
import org.junit.Test;
/**
* Base support for testing stream-based exporters
*
* @author <a href="mailto:baileyje@gmail.com">John Bailey</a>
* @author <a href="mailto:aslak@conduct.no">Aslak Knutsen</a>
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
public abstract class StreamExporterTestBase<T extends StreamImporter<T>> extends ExportTestBase {
// -------------------------------------------------------------------------------------||
// Class Members ----------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Logger
*/
private static final Logger log = Logger.getLogger(StreamExporterTestBase.class.getName());
// -------------------------------------------------------------------------------------||
// Contracts --------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Ensures the contents of the specified {@link File} are as expected
*
* @param file
* @throws IOException
* If an I/O error occurred
*/
protected abstract void ensureInExpectedForm(File file) throws IOException;
/**
* Obtains the {@link Asset} located at the specified {@link ArchivePath} in the specified {@link File}, or null if
* nothing is found at the specified path
*
* @param file
* @param path
* @return
*/
protected abstract InputStream getContentsFromExportedFile(File file, ArchivePath path) throws IOException;
/**
* Obtains the type of {@link StreamImporter} used for this test
*
* @return
*/
protected abstract Class<T> getImporterClass();
// -------------------------------------------------------------------------------------||
// Tests ------------------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Test to make sue an archive can be exported and all contents are correctly located.
*
* @throws Exception
*/
@Test
public void testExport() throws Exception {
log.info("testExport");
// Get an archive instance
Archive<?> archive = createArchiveWithAssets();
// Export as InputStream
final InputStream exportStream = this.exportAsInputStream(archive);
// Validate
final File tempDirectory = createTempDirectory("testExport");
final File serialized = new File(tempDirectory, archive.getName());
final FileOutputStream out = new FileOutputStream(serialized);
IOUtil.copyWithClose(exportStream, out);
ensureInExpectedForm(serialized);
}
/**
* Test to ensure that the export process accepts an archive with only directories, no assets.
*
* @throws Exception
*/
@Test
public void testExportArchiveWithOnlyDirectories() throws IOException {
// Create an archive with directories
final ArchivePath path = ArchivePaths.create("/test/game");
final Archive<?> archive = ShrinkWrap.create(JavaArchive.class, NAME_ARCHIVE).addAsDirectories(path);
// Fully export by reading all content (export is on-demand)
final InputStream content = this.exportAsInputStream(archive);
final ByteArrayOutputStream exportedContents = new ByteArrayOutputStream();
IOUtil.copyWithClose(content, exportedContents);
final GenericArchive roundtrip = ShrinkWrap.create(this.getImporterClass(), "roundtrip.zip")
.importFrom(new ByteArrayInputStream(exportedContents.toByteArray())).as(GenericArchive.class);
log.info(roundtrip.toString(true));
Assert.assertTrue(roundtrip.contains(path));
}
/**
* Test to make sure an archive can be exported to file and all contents are correctly located.
*
* @throws Exception
*/
@Test
public void testExportToFile() throws IOException {
log.info("testExportToFile");
// Get a temp directory for the test
File tempDirectory = createTempDirectory("testExportToFile");
// Get an archive instance
Archive<?> archive = createArchiveWithAssets();
// Export as File
final File exported = new File(tempDirectory, archive.getName());
this.exportAsFile(archive, exported, true);
// Roundtrip assertion
this.ensureInExpectedForm(exported);
}
/**
* Ensures that we get an {@link IllegalArgumentException} if we attempt to export to a directory
*
* @throws IOException
*/
@Test
public void testExportToDirectoryFails() throws IOException {
log.info("testExportToDirectoryFails");
// Get a temp directory for the test
File tempDirectory = createTempDirectory("testExportToDirectoryFails");
// Get an archive instance
Archive<?> archive = createArchiveWithAssets();
// Export as File to a directory
try {
this.exportAsFile(archive, tempDirectory, true);
}
// Expected
catch (final IllegalArgumentException iae) {
// Good
return;
}
// Fail
Assert
.fail("Should have encountered " + IllegalArgumentException.class.getSimpleName() + " exporting to a dir");
}
/**
* Test to make sure an archive can be exported to a {@link OutputStream} and all contents are correctly located.
*
* @throws Exception
*/
@Test
public void testExportToOutStream() throws IOException {
log.info("testExportToOutStream");
// Get a temp directory for the test
final File tempDirectory = createTempDirectory("testExportToOutStream");
// Get an archive instance
final Archive<?> archive = createArchiveWithAssets();
// Export as OutStream and flush to a file manually
final File serializedArchive = new File(tempDirectory, archive.getName());
final OutputStream out = new FileOutputStream(serializedArchive);
this.exportToOutputStream(archive, out);
// Validate
this.ensureInExpectedForm(serializedArchive);
}
/**
* Test to make sure an archive can be exported to file and all contents are correctly located.
*
* @throws Exception
*/
@Test
public void testExportToExistingFileFails() throws IOException {
log.info("testExportToExistingFileFails");
// Get a temp directory for the test
final File tempDirectory = createTempDirectory("testExportToExistingFileFails");
// Get an archive instance
final Archive<?> archive = createArchiveWithAssets();
// Export as File
final File alreadyExists = new File(tempDirectory, archive.getName());
final OutputStream alreadyExistsOutputStream = new FileOutputStream(alreadyExists);
alreadyExistsOutputStream.write(new byte[] {});
alreadyExistsOutputStream.close();
Assert.assertTrue("The test setup is incorrect; an empty file should exist before writing the archive",
alreadyExists.exists());
// Should fail, as we're not overwriting
boolean gotExpectedException = false;
try {
this.exportAsFile(archive, alreadyExists, false);
} catch (final FileExistsException fee) {
gotExpectedException = true;
}
Assert.assertTrue("Should get " + FileExistsException.class.getSimpleName()
+ " when exporting to an existing file when overwrite is false", gotExpectedException);
}
/**
* Test to make sue an archive can be exported and nested archives are also in exported.
*
* @throws Exception
*/
@Test
public void testExportNested() throws Exception {
log.info("testExportNested");
// Get a temp directory for the test
final File tempDirectory = createTempDirectory("testExportNested");
// Get an archive instance
final Archive<?> archive = createArchiveWithNestedArchives();
// Export as InputStream
final InputStream exportStream = this.exportAsInputStream(archive);
// Write out and retrieve as exported file
final File exported = new File(tempDirectory, NAME_ARCHIVE + this.getArchiveExtension());
final OutputStream exportedOut = new FileOutputStream(exported);
IOUtil.copyWithClose(exportStream, exportedOut);
// Validate entries were written out
this.ensureAssetInExportedFile(exported, PATH_ONE, ASSET_ONE);
this.ensureAssetInExportedFile(exported, PATH_TWO, ASSET_TWO);
// Validate nested archive entries were written out
final ArchivePath nestedArchivePath = ArchivePaths.create(NAME_NESTED_ARCHIVE + this.getArchiveExtension());
// Get inputstream for entry
final InputStream nestedArchiveStream = this.getContentsFromExportedFile(exported, nestedArchivePath);
// Write out and retrieve nested contents
final File nestedFile = new File(tempDirectory, NAME_NESTED_ARCHIVE + this.getArchiveExtension());
final OutputStream nestedOut = new FileOutputStream(nestedFile);
IOUtil.copyWithClose(nestedArchiveStream, nestedOut);
// Ensure contents are in the nested
this.ensureAssetInExportedFile(nestedFile, PATH_ONE, ASSET_ONE);
this.ensureAssetInExportedFile(nestedFile, PATH_TWO, ASSET_TWO);
// Validate nested archive entries were written out
final ArchivePath nestedArchiveTwoPath = ArchivePaths.create(NESTED_PATH,
NAME_NESTED_ARCHIVE_2 + this.getArchiveExtension());
this.getContentsFromExportedFile(exported, nestedArchiveTwoPath);
final InputStream nestedArchiveTwoStream = this.getContentsFromExportedFile(exported, nestedArchiveTwoPath);
// Write out and retrieve secondnested contents
final File nestedTwoFile = new File(tempDirectory, NAME_NESTED_ARCHIVE_2 + this.getArchiveExtension());
final OutputStream nestedTwoOut = new FileOutputStream(nestedTwoFile);
IOUtil.copyWithClose(nestedArchiveTwoStream, nestedTwoOut);
// Ensure contents are in the second nested
this.ensureAssetInExportedFile(nestedTwoFile, PATH_ONE, ASSET_ONE);
this.ensureAssetInExportedFile(nestedTwoFile, PATH_TWO, ASSET_TWO);
}
@Test(expected = ArchiveExportException.class)
public void testExportThrowsArchiveExceptionOnAssetWriteFailure() throws IOException {
log.info("testExportThrowsArchiveExceptionOnAssetWriteFailure");
Archive<?> archive = createArchiveWithAssets();
// Check if a the path already contains a node so we remove it from the parent's children
if (archive.contains(PATH_ONE)) {
archive.delete(PATH_ONE);
}
archive.add(new Asset() {
@Override
public InputStream openStream() {
throw new RuntimeException("Mock Exception from an Asset write");
}
}, PATH_ONE);
// Export
final InputStream in = this.exportAsInputStream(archive);
// Read in the full content (to in turn empty the underlying buffer and ensure we complete)
final OutputStream sink = new OutputStream() {
@Override
public void write(int b) throws IOException {
}
};
IOUtil.copyWithClose(in, sink);
}
// -------------------------------------------------------------------------------------||
// Helper Methods ---------------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
/**
* Exports the specified archive as an {@link InputStream}
*/
private InputStream exportAsInputStream(final Archive<?> archive) {
assert archive != null : "archive must be specified";
final Class<? extends StreamExporter> exporter = this.getExporterClass();
assert exporter != null : "Exporter class must be specified";
return archive.as(this.getExporterClass()).exportAsInputStream();
}
/**
* Exports the specified archive as a {@link File}, overwriting an existing one is specified
*
* @param archive
* @param file
* @param overwrite
*/
private void exportAsFile(final Archive<?> archive, final File file, final boolean overwrite) {
// Precondition checks
assert file != null : "file must be specified";
assert archive != null : "archive must be specified";
// Export
final Class<? extends StreamExporter> exporter = this.getExporterClass();
assert exporter != null : "Exporter class must be specified";
archive.as(exporter).exportTo(file, overwrite);
}
/**
* Exports the specified archive to an {@link OutputStream}
*
* @param archive
* @return
*/
private void exportToOutputStream(final Archive<?> archive, final OutputStream out) {
assert archive != null : "archive must be specified";
assert out != null : "outstream must be specified";
// Export
final Class<? extends StreamExporter> exporter = this.getExporterClass();
assert exporter != null : "Exporter class must be specified";
try {
archive.as(exporter).exportTo(out);
} finally {
try {
out.close();
} catch (final IOException ioe) {
log.warning("Could not close " + out + ": " + ioe);
}
}
}
/**
* Test implementation of an {@link ExecutorService} which counts all jobs submitted.
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
private static class CountingExecutorService implements ExecutorService {
private final ExecutorService delegate;
int counter = 0;
public CountingExecutorService() {
delegate = Executors.newSingleThreadExecutor();
}
@Override
public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
return delegate.awaitTermination(timeout, unit);
}
@Override
public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks) throws InterruptedException {
return delegate.invokeAll(tasks);
}
@Override
public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks, final long timeout,
final TimeUnit unit) throws InterruptedException {
return delegate.invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(final Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return delegate.invokeAny(tasks);
}
@Override
public <T> T invokeAny(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return delegate.invokeAny(tasks, timeout, unit);
}
@Override
public boolean isShutdown() {
return delegate.isShutdown();
}
@Override
public boolean isTerminated() {
return delegate.isTerminated();
}
@Override
public void shutdown() {
delegate.shutdown();
}
@Override
public List<Runnable> shutdownNow() {
return delegate.shutdownNow();
}
@Override
public <T> Future<T> submit(final Callable<T> task) {
counter++;
return delegate.submit(task);
}
@Override
public Future<?> submit(final Runnable task) {
counter++;
return delegate.submit(task);
}
@Override
public <T> Future<T> submit(final Runnable task, final T result) {
counter++;
return delegate.submit(task, result);
}
@Override
public void execute(final Runnable command) {
counter++;
delegate.execute(command);
}
}
/**
* {@inheritDoc}
*
* @see org.jboss.shrinkwrap.impl.base.exporter.StreamExporterTestBase#ensureAssetInExportedFile(java.io.File,
* org.jboss.shrinkwrap.api.ArchivePath, org.jboss.shrinkwrap.api.asset.Asset)
*/
protected final void ensureAssetInExportedFile(final File file, final ArchivePath path, final Asset asset)
throws IOException {
// Precondition checks
assert file != null : "file must be specified";
assert path != null : "path must be specified";
assert asset != null : "asset must be specified";
// Get as Exported File
final InputStream actualStream = this.getContentsFromExportedFile(file, path);
assert actualStream != null : "No contents found at path " + path + " in " + file.getAbsolutePath();
byte[] actualContents = IOUtil.asByteArray(actualStream);
byte[] expectedContents = IOUtil.asByteArray(asset.openStream());
Assert.assertArrayEquals(expectedContents, actualContents);
}
}