001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thierry Delprat <tdelprat@nuxeo.com> 016 * Antoine Taillefer <ataillefer@nuxeo.com> 017 * 018 */ 019package org.nuxeo.ecm.automation.server.jaxrs.batch; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.Comparator; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.commons.io.FileUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.Blobs; 036import org.nuxeo.ecm.core.transientstore.api.TransientStore; 037import org.nuxeo.runtime.api.Framework; 038 039/** 040 * Batch Object to encapsulate all data related to a batch, especially the temporary files used for Blobs. 041 * <p> 042 * Since 7.4 a batch is backed by the {@link TransientStore}. 043 * 044 * @since 5.4.2 045 */ 046public class Batch { 047 048 protected static final Log log = LogFactory.getLog(Batch.class); 049 050 public static final String CHUNKED_PARAM_NAME = "chunked"; 051 052 protected String key; 053 054 protected Map<String, Serializable> fileEntries; 055 056 public Batch(String key) { 057 this(key, new HashMap<>()); 058 } 059 060 public Batch(String key, Map<String, Serializable> fileEntries) { 061 this.key = key; 062 this.fileEntries = fileEntries; 063 } 064 065 public String getKey() { 066 return key; 067 } 068 069 /** 070 * Returns the uploaded blobs in the order the user chose to upload them. 071 */ 072 public List<Blob> getBlobs() { 073 List<Blob> blobs = new ArrayList<Blob>(); 074 List<String> sortedFileIndexes = getOrderedFileIndexes(); 075 log.debug(String.format("Retrieving blobs for batch %s: %s", key, sortedFileIndexes)); 076 for (String index : sortedFileIndexes) { 077 Blob blob = retrieveBlob(index); 078 if (blob != null) { 079 blobs.add(blob); 080 } 081 } 082 return blobs; 083 } 084 085 public Blob getBlob(String index) { 086 log.debug(String.format("Retrieving blob %s for batch %s", index, key)); 087 return retrieveBlob(index); 088 } 089 090 protected List<String> getOrderedFileIndexes() { 091 List<String> sortedFileIndexes = new ArrayList<String>(); 092 sortedFileIndexes = new ArrayList<String>(fileEntries.keySet()); 093 Collections.sort(sortedFileIndexes, new Comparator<String>() { 094 @Override 095 public int compare(String o1, String o2) { 096 return Integer.valueOf(o1).compareTo(Integer.valueOf(o2)); 097 } 098 }); 099 return sortedFileIndexes; 100 } 101 102 protected Blob retrieveBlob(String index) { 103 Blob blob = null; 104 BatchFileEntry fileEntry = getFileEntry(index); 105 if (fileEntry != null) { 106 blob = fileEntry.getBlob(); 107 } 108 return blob; 109 } 110 111 public List<BatchFileEntry> getFileEntries() { 112 List<BatchFileEntry> batchFileEntries = new ArrayList<BatchFileEntry>(); 113 List<String> sortedFileIndexes = getOrderedFileIndexes(); 114 for (String index : sortedFileIndexes) { 115 BatchFileEntry fileEntry = getFileEntry(index); 116 if (fileEntry != null) { 117 batchFileEntries.add(fileEntry); 118 } 119 } 120 return batchFileEntries; 121 } 122 123 public BatchFileEntry getFileEntry(String index) { 124 return getFileEntry(index, true); 125 } 126 127 public BatchFileEntry getFileEntry(String index, boolean fetchBlobs) { 128 BatchManager bm = Framework.getService(BatchManager.class); 129 String fileEntryKey = (String) fileEntries.get(index); 130 if (fileEntryKey == null) { 131 return null; 132 } 133 TransientStore ts = bm.getTransientStore(); 134 Map<String, Serializable> fileEntryParams = ts.getParameters(fileEntryKey); 135 if (fileEntryParams == null) { 136 return null; 137 } 138 boolean chunked = Boolean.parseBoolean((String) fileEntryParams.get(CHUNKED_PARAM_NAME)); 139 if (chunked) { 140 return new BatchFileEntry(fileEntryKey, fileEntryParams); 141 } else { 142 Blob blob = null; 143 if (fetchBlobs) { 144 List<Blob> fileEntryBlobs = ts.getBlobs(fileEntryKey); 145 if (fileEntryBlobs == null) { 146 return null; 147 } 148 if (!fileEntryBlobs.isEmpty()) { 149 blob = fileEntryBlobs.get(0); 150 } 151 } 152 return new BatchFileEntry(fileEntryKey, blob); 153 } 154 } 155 156 /** 157 * Adds a file with the given {@code index} to the batch. 158 * 159 * @return The key of the new {@link BatchFileEntry}. 160 */ 161 public String addFile(String index, InputStream is, String name, String mime) throws IOException { 162 String mimeType = mime; 163 if (mimeType == null) { 164 mimeType = "application/octet-stream"; 165 } 166 Blob blob = Blobs.createBlob(is, mime); 167 blob.setFilename(name); 168 169 String fileEntryKey = key + "_" + index; 170 BatchManager bm = Framework.getService(BatchManager.class); 171 TransientStore ts = bm.getTransientStore(); 172 ts.putBlobs(fileEntryKey, Collections.singletonList(blob)); 173 ts.putParameter(fileEntryKey, CHUNKED_PARAM_NAME, String.valueOf(false)); 174 ts.putParameter(key, index, fileEntryKey); 175 176 return fileEntryKey; 177 } 178 179 /** 180 * Adds a chunk with the given {@code chunkIndex} to the batch file with the given {@code index}. 181 * 182 * @return The key of the {@link BatchFileEntry}. 183 * @since 7.4 184 */ 185 public String addChunk(String index, InputStream is, int chunkCount, int chunkIndex, String fileName, 186 String mimeType, long fileSize) throws IOException { 187 BatchManager bm = Framework.getService(BatchManager.class); 188 Blob blob = Blobs.createBlob(is); 189 190 String fileEntryKey = key + "_" + index; 191 BatchFileEntry fileEntry = getFileEntry(index); 192 if (fileEntry == null) { 193 fileEntry = new BatchFileEntry(fileEntryKey, chunkCount, fileName, mimeType, fileSize); 194 TransientStore ts = bm.getTransientStore(); 195 ts.putParameters(fileEntryKey, fileEntry.getParams()); 196 ts.putParameter(key, index, fileEntryKey); 197 } 198 fileEntry.addChunk(chunkIndex, blob); 199 200 return fileEntryKey; 201 } 202 203 /** 204 * @since 7.4 205 */ 206 public void clean() { 207 // Remove batch and all related storage entries from transient store, GC will clean up the files 208 log.debug(String.format("Cleaning batch %s", key)); 209 BatchManager bm = Framework.getService(BatchManager.class); 210 TransientStore ts = bm.getTransientStore(); 211 for (String fileIndex : fileEntries.keySet()) { 212 // Check for chunk entries to remove 213 BatchFileEntry fileEntry = (BatchFileEntry) getFileEntry(fileIndex, false); 214 if (fileEntry != null) { 215 if (fileEntry.isChunked()) { 216 for (String chunkEntryKey : fileEntry.getChunkEntryKeys()) { 217 // Remove chunk entry from the store and delete blobs from the file system 218 List<Blob> chunkBlobs = ts.getBlobs(chunkEntryKey); 219 if (chunkBlobs != null) { 220 for (Blob blob : chunkBlobs) { 221 try { 222 FileUtils.deleteDirectory(blob.getFile().getParentFile()); 223 } catch (IOException e) { 224 log.error("Error while deleting chunk parent directory", e); 225 } 226 } 227 } 228 ts.remove(chunkEntryKey); 229 } 230 fileEntry.beforeRemove(); 231 } 232 // Remove file entry from the store and delete blobs from the file system 233 String fileEntryKey = fileEntry.getKey(); 234 List<Blob> fileBlobs = ts.getBlobs(fileEntryKey); 235 if (fileBlobs != null) { 236 for (Blob blob : fileBlobs) { 237 try { 238 FileUtils.deleteDirectory(blob.getFile().getParentFile()); 239 } catch (IOException e) { 240 log.error("Error while deleting file parent directory", e); 241 } 242 } 243 } 244 ts.remove(fileEntryKey); 245 } 246 } 247 // Remove batch entry 248 ts.remove(key); 249 } 250 251}