001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 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 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.api.impl.blob; 021 022import java.io.BufferedInputStream; 023import java.io.Closeable; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.List; 030import java.util.zip.ZipEntry; 031import java.util.zip.ZipFile; 032import java.util.zip.ZipInputStream; 033 034import org.apache.commons.io.input.ProxyInputStream; 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.api.CloseableFile; 037import org.nuxeo.ecm.core.api.NuxeoException; 038 039/** 040 * A {@link Blob} backed by an entry in a ZIP file. 041 * 042 * @since 7.2 043 */ 044public class ZipEntryBlob extends AbstractBlob implements Serializable { 045 046 private static final long serialVersionUID = 1L; 047 048 protected final ZipFile zipFile; 049 050 protected final ZipEntry zipEntry; 051 052 protected final Blob zipBlob; 053 054 protected final String entryName; 055 056 /** 057 * Creates a {@link Blob} from an entry in a zip file. The {@link ZipFile} must not be closed until the stream has 058 * been read. 059 * 060 * @param zipFile the zip file 061 * @param zipEntry the zip entry 062 */ 063 public ZipEntryBlob(ZipFile zipFile, ZipEntry zipEntry) { 064 this.zipFile = zipFile; 065 this.zipEntry = zipEntry; 066 zipBlob = null; 067 entryName = null; 068 } 069 070 /** 071 * Creates a {@link Blob} from an entry in a ZIP file blob. 072 * 073 * @param zipBlob the ZIP file blob 074 * @param entryName the ZIP entry name 075 * @since 11.5 076 */ 077 public ZipEntryBlob(Blob zipBlob, String entryName) { 078 zipFile = null; 079 zipEntry = null; 080 this.zipBlob = zipBlob; 081 this.entryName = entryName; 082 } 083 084 @Override 085 public InputStream getStream() throws IOException { 086 if (zipBlob == null) { 087 return zipFile.getInputStream(zipEntry); 088 } else { 089 return ZipEntryInputStream.of(zipBlob, entryName); 090 } 091 } 092 093 @Override 094 public long getLength() { 095 if (zipBlob == null) { 096 return zipEntry.getSize(); 097 } else { 098 File file = zipBlob.getFile(); 099 if (file != null) { 100 // if there's a file then we can be fast 101 try (ZipFile zf = new ZipFile(file)) { 102 ZipEntry entry = zf.getEntry(entryName); 103 return entry == null ? 0 : entry.getSize(); 104 } catch (IOException e) { 105 throw new NuxeoException(e); 106 } 107 } else { 108 // else use the stream 109 try (ZipInputStream zin = new ZipInputStream(new BufferedInputStream(zipBlob.getStream()))) { 110 ZipEntry entry; 111 while ((entry = zin.getNextEntry()) != null) { 112 if (entry.getName().equals(entryName)) { 113 return entry.getSize(); 114 } 115 } 116 return 0; 117 } catch (IOException e) { 118 throw new NuxeoException(e); 119 } 120 } 121 } 122 } 123 124 // @since 11.5 125 @Override 126 public String getFilename() { 127 return entryName != null ? entryName : super.getFilename(); 128 } 129 130 /** 131 * {@link InputStream} for a ZIP entry, that closes all necessary resources when {@linkplain #close closed}. 132 * 133 * @since 11.5 134 */ 135 public static class ZipEntryInputStream extends ProxyInputStream { 136 137 // what we'll need to close in addition to the InputStream itself 138 protected final List<Closeable> closeables; 139 140 /** 141 * Factory for a {@link ZipEntryInputStream}. 142 */ 143 public static ZipEntryInputStream of(Blob zipBlob, String entryName) throws IOException { 144 List<Closeable> closeables = new ArrayList<>(2); 145 InputStream in; 146 try { 147 CloseableFile closeableFile = zipBlob.getCloseableFile(); 148 closeables.add(closeableFile); 149 ZipFile zipFile = new ZipFile(closeableFile.getFile()); 150 closeables.add(zipFile); 151 in = zipFile.getInputStream(zipFile.getEntry(entryName)); 152 } catch (IOException ioe) { 153 try { 154 close(closeables); 155 } catch (IOException e) { 156 ioe.addSuppressed(e); 157 } 158 throw ioe; 159 } 160 return new ZipEntryInputStream(in, closeables); 161 } 162 163 protected ZipEntryInputStream(InputStream in, List<Closeable> closeables) { 164 super(in); 165 this.closeables = closeables; 166 } 167 168 @Override 169 public void close() throws IOException { 170 List<Closeable> closing = new ArrayList<>(3); 171 closing.add(super::close); 172 closing.addAll(closeables); 173 closeables.clear(); 174 close(closing); 175 } 176 177 protected static void close(List<Closeable> closeables) throws IOException { 178 IOException ioe = null; 179 for (Closeable closeable : closeables) { 180 try { 181 closeable.close(); 182 } catch (IOException e) { 183 if (ioe == null) { 184 ioe = e; 185 } else { 186 ioe.addSuppressed(e); 187 } 188 } 189 } 190 if (ioe != null) { 191 throw ioe; 192 } 193 } 194 } 195 196}