001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.modeshape.sequencer.zip; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.util.zip.ZipEntry; 022import java.util.zip.ZipInputStream; 023import javax.jcr.Binary; 024import javax.jcr.NamespaceRegistry; 025import javax.jcr.Node; 026import javax.jcr.PathNotFoundException; 027import javax.jcr.Property; 028import javax.jcr.RepositoryException; 029import org.modeshape.common.util.CheckArg; 030import org.modeshape.jcr.api.JcrConstants; 031import org.modeshape.jcr.api.nodetype.NodeTypeManager; 032import org.modeshape.jcr.api.sequencer.Sequencer; 033 034/** 035 * A sequencer that processes and extract the files and folders from ZIP archive files. 036 * 037 * @author Horia Chiorean 038 */ 039public class ZipSequencer extends Sequencer { 040 041 public static final class MimeTypeConstants { 042 public static final String JAR = "application/java-archive"; 043 public static final String ZIP = "application/zip"; 044 } 045 046 @Override 047 public void initialize( NamespaceRegistry registry, 048 NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException { 049 super.registerNodeTypes("zip.cnd", nodeTypeManager, true); 050 registerDefaultMimeTypes(MimeTypeConstants.JAR, MimeTypeConstants.ZIP); 051 } 052 053 @Override 054 public boolean execute( Property inputProperty, 055 Node outputNode, 056 Context context ) throws Exception { 057 Binary binaryValue = inputProperty.getBinary(); 058 CheckArg.isNotNull(binaryValue, "binary"); 059 060 try (ZipInputStream zipInputStream = new ZipInputStream(binaryValue.getStream())){ 061 ZipEntry entry = zipInputStream.getNextEntry(); 062 outputNode = createTopLevelNode(outputNode); 063 while (entry != null) { 064 entry = sequenceZipEntry(outputNode, context, zipInputStream, entry); 065 } 066 return true; 067 } 068 } 069 070 private Node createTopLevelNode( Node outputNode ) throws RepositoryException { 071 // Create top-level node 072 if (!outputNode.isNew()) { 073 outputNode = outputNode.addNode(ZipLexicon.CONTENT); 074 } 075 outputNode.setPrimaryType(ZipLexicon.FILE); 076 return outputNode; 077 } 078 079 private ZipEntry sequenceZipEntry( Node outputNode, 080 Context context, 081 ZipInputStream zipInputStream, 082 ZipEntry entry ) throws RepositoryException, IOException { 083 Node zipEntryNode = createZipEntryPath(outputNode, entry); 084 085 if (!entry.isDirectory()) { 086 addFileContent(zipInputStream, entry, context, zipEntryNode); 087 } 088 return zipInputStream.getNextEntry(); 089 } 090 091 private void addFileContent( ZipInputStream zipInputStream, 092 ZipEntry entry, 093 Context context, 094 Node zipFileNode ) throws RepositoryException, IOException { 095 Node contentNode = zipFileNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE); 096 // on session pre-save the appropriate properties should be set automatically 097 contentNode.addMixin(JcrConstants.MIX_LAST_MODIFIED); 098 099 // set the content bytes 100 byte[] contentBytes = readContent(zipInputStream); 101 org.modeshape.jcr.api.Binary contentBinary = context.valueFactory().createBinary(contentBytes); 102 contentNode.setProperty(JcrConstants.JCR_DATA, contentBinary); 103 104 // Figure out the mime type ... 105 String mimeType = contentBinary.getMimeType(entry.getName()); 106 if (mimeType != null) { 107 contentNode.setProperty(JcrConstants.JCR_MIME_TYPE, mimeType); 108 } 109 } 110 111 /** 112 * Reads the content from the {@link ZipInputStream}, making sure it doesn't close the stream. 113 * 114 * @param zipInputStream the input stream 115 * @return the content 116 * @throws IOException if there is a problem reading the stream 117 */ 118 private byte[] readContent( ZipInputStream zipInputStream ) throws IOException { 119 int bufferLength = 1024; 120 byte[] buffer = new byte[bufferLength]; 121 int n; 122 ByteArrayOutputStream baout = new ByteArrayOutputStream(); 123 while ((n = zipInputStream.read(buffer, 0, bufferLength)) > -1) { 124 baout.write(buffer, 0, n); 125 } 126 return baout.toByteArray(); 127 } 128 129 /** 130 * Creates (if necessary) the path from the {@link Node parentNode} to the {@link ZipEntry zip entry}, based on the name of 131 * the zip entry, which should contain its absolute path. 132 * 133 * @param parentNode the parent node under which the node for the ZIP entry should be created 134 * @param entry the ZIP file entry 135 * @return the newly created node 136 * @throws RepositoryException if there is a problem writing the content to the repository session 137 */ 138 private Node createZipEntryPath( Node parentNode, 139 ZipEntry entry ) throws RepositoryException { 140 Node zipEntryNode = parentNode; 141 String entryName = entry.getName(); 142 String[] segments = entryName.split("/"); 143 for (int i = 0; i < segments.length; i++) { 144 String segmentName = segments[i]; 145 try { 146 zipEntryNode = zipEntryNode.getNode(segmentName); 147 } catch (PathNotFoundException e) { 148 // the path does not exist yet - create it 149 boolean isLastSegment = (i == segments.length - 1); 150 String segmentPrimaryType = isLastSegment ? (entry.isDirectory() ? JcrConstants.NT_FOLDER : JcrConstants.NT_FILE) : JcrConstants.NT_FOLDER; 151 zipEntryNode = zipEntryNode.addNode(segmentName, segmentPrimaryType); 152 } 153 } 154 return zipEntryNode; 155 } 156}