/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.sequencer.zip;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.jcr.Binary;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.api.JcrConstants;
import org.modeshape.jcr.api.nodetype.NodeTypeManager;
import org.modeshape.jcr.api.sequencer.Sequencer;
/**
* A sequencer that processes and extract the files and folders from ZIP archive files.
*
* @author Horia Chiorean
*/
public class ZipSequencer extends Sequencer {
public static final class MimeTypeConstants {
public static final String JAR = "application/java-archive";
public static final String ZIP = "application/zip";
}
@Override
public void initialize( NamespaceRegistry registry,
NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException {
super.registerNodeTypes("zip.cnd", nodeTypeManager, true);
registerDefaultMimeTypes(MimeTypeConstants.JAR, MimeTypeConstants.ZIP);
}
@Override
public boolean execute( Property inputProperty,
Node outputNode,
Context context ) throws Exception {
Binary binaryValue = inputProperty.getBinary();
CheckArg.isNotNull(binaryValue, "binary");
try (ZipInputStream zipInputStream = new ZipInputStream(binaryValue.getStream())){
ZipEntry entry = zipInputStream.getNextEntry();
outputNode = createTopLevelNode(outputNode);
while (entry != null) {
entry = sequenceZipEntry(outputNode, context, zipInputStream, entry);
}
return true;
}
}
private Node createTopLevelNode( Node outputNode ) throws RepositoryException {
// Create top-level node
if (!outputNode.isNew()) {
outputNode = outputNode.addNode(ZipLexicon.CONTENT);
}
outputNode.setPrimaryType(ZipLexicon.FILE);
return outputNode;
}
private ZipEntry sequenceZipEntry( Node outputNode,
Context context,
ZipInputStream zipInputStream,
ZipEntry entry ) throws RepositoryException, IOException {
Node zipEntryNode = createZipEntryPath(outputNode, entry);
if (!entry.isDirectory()) {
addFileContent(zipInputStream, entry, context, zipEntryNode);
}
return zipInputStream.getNextEntry();
}
private void addFileContent( ZipInputStream zipInputStream,
ZipEntry entry,
Context context,
Node zipFileNode ) throws RepositoryException, IOException {
Node contentNode = zipFileNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
// on session pre-save the appropriate properties should be set automatically
contentNode.addMixin(JcrConstants.MIX_LAST_MODIFIED);
// set the content bytes
byte[] contentBytes = readContent(zipInputStream);
org.modeshape.jcr.api.Binary contentBinary = context.valueFactory().createBinary(contentBytes);
contentNode.setProperty(JcrConstants.JCR_DATA, contentBinary);
// Figure out the mime type ...
String mimeType = contentBinary.getMimeType(entry.getName());
if (mimeType != null) {
contentNode.setProperty(JcrConstants.JCR_MIME_TYPE, mimeType);
}
}
/**
* Reads the content from the {@link ZipInputStream}, making sure it doesn't close the stream.
*
* @param zipInputStream the input stream
* @return the content
* @throws IOException if there is a problem reading the stream
*/
private byte[] readContent( ZipInputStream zipInputStream ) throws IOException {
int bufferLength = 1024;
byte[] buffer = new byte[bufferLength];
int n;
ByteArrayOutputStream baout = new ByteArrayOutputStream();
while ((n = zipInputStream.read(buffer, 0, bufferLength)) > -1) {
baout.write(buffer, 0, n);
}
return baout.toByteArray();
}
/**
* Creates (if necessary) the path from the {@link Node parentNode} to the {@link ZipEntry zip entry}, based on the name of
* the zip entry, which should contain its absolute path.
*
* @param parentNode the parent node under which the node for the ZIP entry should be created
* @param entry the ZIP file entry
* @return the newly created node
* @throws RepositoryException if there is a problem writing the content to the repository session
*/
private Node createZipEntryPath( Node parentNode,
ZipEntry entry ) throws RepositoryException {
Node zipEntryNode = parentNode;
String entryName = entry.getName();
String[] segments = entryName.split("/");
for (int i = 0; i < segments.length; i++) {
String segmentName = segments[i];
try {
zipEntryNode = zipEntryNode.getNode(segmentName);
} catch (PathNotFoundException e) {
// the path does not exist yet - create it
boolean isLastSegment = (i == segments.length - 1);
String segmentPrimaryType = isLastSegment ? (entry.isDirectory() ? JcrConstants.NT_FOLDER : JcrConstants.NT_FILE) : JcrConstants.NT_FOLDER;
zipEntryNode = zipEntryNode.addNode(segmentName, segmentPrimaryType);
}
}
return zipEntryNode;
}
}