package org.atomnuke.container.packaging.archive.zip;
import com.rackspace.papi.commons.util.io.MessageDigesterOutputStream;
import com.rackspace.papi.commons.util.io.OutputStreamSplitter;
import com.rackspace.papi.commons.util.io.RawInputStreamReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.atomnuke.container.packaging.MissingDigestImplementationException;
import org.atomnuke.container.packaging.archive.ArchiveExtractionException;
import org.atomnuke.container.packaging.archive.ArchiveResource;
import org.atomnuke.container.packaging.archive.ArchiveResourceImpl;
import org.atomnuke.container.packaging.archive.ResourceType;
import org.atomnuke.container.packaging.resource.Resource;
import org.atomnuke.container.packaging.resource.ResourceDescriptorImpl;
import org.atomnuke.container.packaging.resource.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author zinic
*/
public class ZipUnpacker {
private static final Logger LOG = LoggerFactory.getLogger(ZipUnpacker.class);
private static final MessageDigest SHA1_DIGESTER;
static {
try {
SHA1_DIGESTER = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException nsae) {
throw new MissingDigestImplementationException("Required crypto algorithm missing. Exception: " + nsae.getMessage(), nsae);
}
}
private final ResourceManager rootResourceManager;
private final File deploymentRoot;
public ZipUnpacker(ResourceManager rootResourceManager, File deploymentRoot) {
this.rootResourceManager = rootResourceManager;
this.deploymentRoot = deploymentRoot;
}
private static String formatEntryName(String entryName) {
return entryName.endsWith("/") ? entryName.substring(0, entryName.length() - 1) : entryName;
}
public List<EmbeddedArchive> unpack(ResourceManager localResourceManager, URI location, int previousDepth) throws IOException {
final List<EmbeddedArchive> embeddedArchives = new LinkedList<EmbeddedArchive>();
// Open up our input stream
final ZipInputStream rootInputStream = new ZipInputStream(location.toURL().openStream());
ZipEntry entry;
// Read through all of the entries
while ((entry = rootInputStream.getNextEntry()) != null) {
final ArchiveResource resourceInfo = new ArchiveResourceImpl(formatEntryName(entry.getName()));
// We handle directories a little different
if (entry.isDirectory()) {
registerArchiveDirectory(resourceInfo);
} else {
register(localResourceManager, rootInputStream, embeddedArchives, resourceInfo, previousDepth);
}
}
return embeddedArchives;
}
private void registerArchiveDirectory(ArchiveResource archiveResource) throws ArchiveExtractionException {
if (!rootResourceManager.exists(archiveResource.name())) {
final File desiredLocation = new File(deploymentRoot + File.separator + archiveResource.name());
if (!desiredLocation.exists() && !desiredLocation.mkdirs()) {
throw new ArchiveExtractionException("Can't make deployment directories.");
}
final URI locationUri = desiredLocation.toURI();
rootResourceManager.register(new ResourceDescriptorImpl(locationUri, archiveResource.name(), ResourceType.DIRECTORY));
rootResourceManager.alias(archiveResource.name(), locationUri);
}
}
private void register(ResourceManager localResourceManager, ZipInputStream rootInputStream, List<EmbeddedArchive> embeddedArchives, ArchiveResource archiveResource, int previousDepth) throws IOException, FileNotFoundException {
final File fsTargetLocation = new File(deploymentRoot + File.separator + archiveResource.name());
final File parentDir = fsTargetLocation.getParentFile();
if (!parentDir.exists() && !parentDir.mkdirs()) {
throw new IOException("Can't make deployment directory: " + parentDir.getAbsolutePath());
}
// Set up our output streams and extract to the slotted location regardless
final byte[] digest = extractNextArchiveItemTo(rootInputStream, fsTargetLocation).getDigest();
ResourceManager targetResrouceManager = rootResourceManager;
// If there's an instance of this resource, we might need to slot it
final Resource conflictingResource = rootResourceManager.lookup(archiveResource.name());
// Should we slot this resource?
if (conflictingResource != null) {
targetResrouceManager = localResourceManager;
// Does the resource conflict? If not, remove what we just extracted and don't register this resource
if (!rootResourceManager.conflicts(conflictingResource.uri(), digest)) {
fsTargetLocation.delete();
return;
}
LOG.debug("Slotting conflicted resource: " + fsTargetLocation.toURI());
}
// Regsiter our new resource
targetResrouceManager.register(new ResourceDescriptorImpl(fsTargetLocation.toURI(), archiveResource.name(), archiveResource.type(), digest));
targetResrouceManager.alias(archiveResource.name(), fsTargetLocation.toURI());
// Add embedded archives for processing later
switch (archiveResource.type()) {
case EAR:
case JAR:
case ZIP:
embeddedArchives.add(new EmbeddedArchive(archiveResource, fsTargetLocation.toURI(), previousDepth + 1));
break;
}
}
private MessageDigesterOutputStream extractNextArchiveItemTo(ZipInputStream archiveInputStream, File fsTargetLocation) throws IOException, FileNotFoundException {
final MessageDigesterOutputStream digesterStream = new MessageDigesterOutputStream(SHA1_DIGESTER);
final FileOutputStream fout = new FileOutputStream(fsTargetLocation);
// Outputstream splitter
final OutputStreamSplitter outputStreamSplitter = new OutputStreamSplitter(digesterStream, fout);
// Copy to the splitter
RawInputStreamReader.instance().copyTo(archiveInputStream, outputStreamSplitter);
// Close all streams tied to the splitter
outputStreamSplitter.close();
return digesterStream;
}
}