/*******************************************************************************
* Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package de.gebit.integrity.runner.providers;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* This {@link TestResourceProvider} can read archive files (zip/jar format) and provide the Integrity script files
* within these archives transparently to the Test Runner. It can even read archives within archives, up to an arbitrary
* depth.
*
* @author Rene Schneider - initial API and implementation
*
*/
public class ArchiveTestResourceProvider extends AbstractTestResourceProvider {
/**
* The ending of zip archives.
*/
public static final String ARCHIVE_ENDING_ZIP = ".zip";
/**
* The ending of jar archives.
*/
public static final String ARCHIVE_ENDING_JAR = ".jar";
/**
* A map to keep track of opened {@link ZipInputStream}s. Used to close the streams.
*/
Map<InputStream, Stack<ZipInputStream>> openedResourceToStreamsMap = new HashMap<InputStream, Stack<ZipInputStream>>();
/**
* Adds an archive (all contained .integrity files) to the resource provider.
*
* @param anArchiveFile
* the archive file to add
* @param aRecursiveFlag
* whether other archives inside this archive shall be added recursively
* @throws IOException
*/
public void addArchive(File anArchiveFile, boolean aRecursiveFlag) throws IOException {
ZipInputStream tempZipInputStream = new ZipInputStream(new FileInputStream(anArchiveFile));
Stack<String> tempArchiveStack = new Stack<String>();
tempArchiveStack.add(anArchiveFile.getAbsolutePath());
addArchive(tempZipInputStream, anArchiveFile.getAbsolutePath(), aRecursiveFlag, tempArchiveStack);
}
/**
* Recursive-enabled variant of {@link #addArchive(File, boolean)}.
*
* @param anArchiveInputStream
* the archive input stream to read from
* @param aPrefix
* the prefix to prepend in front of the filename inside the archive
* @param aRecursiveFlag
* a flag whether recursive invocation is desired
* @throws IOException
*/
@SuppressWarnings("unchecked")
protected void addArchive(ZipInputStream anArchiveInputStream, String aPrefix, boolean aRecursiveFlag,
Stack<String> anArchiveFileNameStack) throws IOException {
ZipEntry tempEntry = anArchiveInputStream.getNextEntry();
while (tempEntry != null) {
if (!tempEntry.isDirectory()) {
String tempEntryName = aPrefix + "/" + tempEntry.getName();
String tempLowerCaseName = tempEntry.getName().toLowerCase();
if (tempLowerCaseName.endsWith(INTEGRITY_TEST_FILES_SUFFIX)) {
addResource(new ArchivedTestResource(tempEntryName, this, anArchiveFileNameStack,
tempEntry.getName()));
} else if (aRecursiveFlag
&& (tempLowerCaseName.endsWith(ARCHIVE_ENDING_ZIP) || tempLowerCaseName
.endsWith(ARCHIVE_ENDING_JAR))) {
Stack<String> tempSubArchiveStack = (Stack<String>) anArchiveFileNameStack.clone();
tempSubArchiveStack.push(tempEntry.getName());
addArchive(new ZipInputStream(anArchiveInputStream), tempEntryName, aRecursiveFlag,
tempSubArchiveStack);
}
}
tempEntry = anArchiveInputStream.getNextEntry();
}
}
@Override
public InputStream openResource(TestResource aResourceName) throws IOException {
if (!(aResourceName instanceof ArchivedTestResource)) {
throw new IllegalArgumentException("The resource must be an " + ArchivedTestResource.class.getSimpleName());
}
ArchivedTestResource tempResource = (ArchivedTestResource) aResourceName;
Stack<ZipInputStream> tempStreamStack = new Stack<ZipInputStream>();
for (String tempFileName : tempResource.getArchiveFilenameStack()) {
if (tempStreamStack.size() == 0) {
// this is the first entry -> must open the file on the filesystem
tempStreamStack.add(new ZipInputStream(new FileInputStream(new File(tempFileName))));
} else {
// this is an inner entry -> open the stream in the outer stream
if (!findFileInZipInputStream(tempStreamStack.lastElement(), tempFileName)) {
throw new RuntimeException("Could not find inner archive file '" + tempFileName + "'");
}
tempStreamStack.add(new ZipInputStream(tempStreamStack.lastElement()));
}
}
// Now the top element on the stack is the zip file where we can read the resource from
ZipInputStream tempInputStream = tempStreamStack.lastElement();
if (!findFileInZipInputStream(tempInputStream, tempResource.getNameWithinArchive())) {
throw new RuntimeException("Could not find script file '" + tempResource + "' in enclosing archive");
}
openedResourceToStreamsMap.put(tempInputStream, tempStreamStack);
return tempInputStream;
}
@Override
public void closeResource(TestResource aResourceName, InputStream aResourceStream) throws IOException {
Stack<ZipInputStream> tempOpenStreams = openedResourceToStreamsMap.remove(aResourceStream);
for (ZipInputStream tempOpenStream : tempOpenStreams) {
tempOpenStream.close();
}
}
/**
* Searches the given {@link ZipInputStream} for an entry, then stops. This changes the active entry in the stream
* to the entry to find (if found) or no entry (if not found).
*
* @param aStream
* the stream to search in
* @param aFileName
* the entry name
* @return true if the entry was found, false if not
* @throws IOException
*/
protected boolean findFileInZipInputStream(ZipInputStream aStream, String aFileName) throws IOException {
ZipEntry tempEntry = aStream.getNextEntry();
while (tempEntry != null) {
if (!tempEntry.isDirectory()) {
if (aFileName.equals(tempEntry.getName())) {
return true;
}
}
tempEntry = aStream.getNextEntry();
}
return false;
}
/**
* Test resource located within one or multiple archives.
*
*
* @author Rene Schneider - initial API and implementation
*
*/
public static class ArchivedTestResource extends TestResource {
/**
* The stack of archive filenames in which this resource is located.
*/
private Stack<String> archiveFilenameStack;
/**
* The name of the resource within its directly containing archive (top of {@link #archiveFilenameStack}).
*/
private String nameWithinArchive;
/**
* Creates a new instance of {@link ArchivedTestResource}.
*
* @param aName
* the full name of the resource
* @param aProvider
* the provider
* @param anArchiveFilenameStack
* the stack of archives to search within
* @param aNameWithinArchive
* the name of the resource within the directly containing archive
*/
public ArchivedTestResource(String aName, TestResourceProvider aProvider, Stack<String> anArchiveFilenameStack,
String aNameWithinArchive) {
super(aName, aProvider);
archiveFilenameStack = anArchiveFilenameStack;
nameWithinArchive = aNameWithinArchive;
}
public Stack<String> getArchiveFilenameStack() {
return archiveFilenameStack;
}
public String getNameWithinArchive() {
return nameWithinArchive;
}
}
}