001/* 002 * (C) Copyright 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 * Thierry Delprat <tdelprat@nuxeo.com> 018 * Antoine Taillefer <ataillefer@nuxeo.com> 019 * Gabriel Barata <gbarata@nuxeo.com> 020 * 021 */ 022package org.nuxeo.ecm.automation.server.jaxrs.batch; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.Blobs; 038import org.nuxeo.ecm.core.transientstore.api.TransientStore; 039import org.nuxeo.runtime.api.Framework; 040 041/** 042 * Batch Object to encapsulate all data related to a batch, especially the temporary files used for Blobs. 043 * <p> 044 * Since 7.4 a batch is backed by the {@link TransientStore}. 045 * 046 * @since 5.4.2 047 */ 048public class Batch { 049 050 protected static final Log log = LogFactory.getLog(Batch.class); 051 052 public static final String CHUNKED_PARAM_NAME = "chunked"; 053 054 protected String key; 055 056 protected Map<String, Serializable> fileEntries; 057 058 protected String handlerName; 059 060 protected TransientStore transientStore; 061 062 protected Map<String, Object> properties; 063 064 public Batch(String key) { 065 this(key, new HashMap<>()); 066 } 067 068 public Batch(String key, Map<String, Serializable> fileEntries) { 069 this(key, fileEntries, null); 070 } 071 072 /** 073 * Constructs a batch. 074 * 075 * @param key the batch key 076 * @param fileEntries the batch file entries 077 * @param handlerName the batch hrovider name 078 * @since 10.1 079 */ 080 public Batch(String key, Map<String, Serializable> fileEntries, String handlerName) { 081 this(key, fileEntries, handlerName, null); 082 } 083 084 /** 085 * Constructs a batch. 086 * 087 * @param key the batch key 088 * @param fileEntries the batch file entries 089 * @param handlerName the batch hrovider name 090 * @param transientStore the transient store associated with this Batch 091 * @since 10.1 092 */ 093 public Batch(String key, Map<String, Serializable> fileEntries, String handlerName, TransientStore transientStore) { 094 this.key = key; 095 this.fileEntries = fileEntries; 096 this.handlerName = handlerName; 097 if (transientStore == null) { 098 transientStore = Framework.getService(BatchManager.class).getTransientStore(); 099 } 100 this.transientStore = transientStore; 101 this.properties = new HashMap<>(); 102 } 103 104 public String getKey() { 105 return key; 106 } 107 108 /** 109 * Returns the uploaded blobs in the order the user chose to upload them. 110 */ 111 public List<Blob> getBlobs() { 112 List<Blob> blobs = new ArrayList<>(); 113 List<String> sortedFileIndexes = getOrderedFileIndexes(); 114 log.debug(String.format("Retrieving blobs for batch %s: %s", key, sortedFileIndexes)); 115 for (String index : sortedFileIndexes) { 116 Blob blob = retrieveBlob(index); 117 if (blob != null) { 118 blobs.add(blob); 119 } 120 } 121 return blobs; 122 } 123 124 public Blob getBlob(String index) { 125 log.debug(String.format("Retrieving blob %s for batch %s", index, key)); 126 return retrieveBlob(index); 127 } 128 129 protected List<String> getOrderedFileIndexes() { 130 List<String> sortedFileIndexes = new ArrayList<>(fileEntries.keySet()); 131 sortedFileIndexes.sort(Comparator.comparing(Integer::valueOf)); 132 return sortedFileIndexes; 133 } 134 135 protected Blob retrieveBlob(String index) { 136 Blob blob = null; 137 BatchFileEntry fileEntry = getFileEntry(index); 138 if (fileEntry != null) { 139 blob = fileEntry.getBlob(); 140 } 141 return blob; 142 } 143 144 public List<BatchFileEntry> getFileEntries() { 145 List<BatchFileEntry> batchFileEntries = new ArrayList<>(); 146 List<String> sortedFileIndexes = getOrderedFileIndexes(); 147 for (String index : sortedFileIndexes) { 148 BatchFileEntry fileEntry = getFileEntry(index); 149 if (fileEntry != null) { 150 batchFileEntries.add(fileEntry); 151 } 152 } 153 return batchFileEntries; 154 } 155 156 public BatchFileEntry getFileEntry(String index) { 157 return getFileEntry(index, true); 158 } 159 160 public BatchFileEntry getFileEntry(String index, boolean fetchBlobs) { 161 String fileEntryKey = (String) fileEntries.get(index); 162 if (fileEntryKey == null) { 163 return null; 164 } 165 Map<String, Serializable> fileEntryParams = transientStore.getParameters(fileEntryKey); 166 if (fileEntryParams == null) { 167 return null; 168 } 169 boolean chunked = Boolean.parseBoolean((String) fileEntryParams.get(CHUNKED_PARAM_NAME)); 170 if (chunked) { 171 return new BatchFileEntry(fileEntryKey, fileEntryParams); 172 } else { 173 Blob blob = null; 174 if (fetchBlobs) { 175 List<Blob> fileEntryBlobs = transientStore.getBlobs(fileEntryKey); 176 if (fileEntryBlobs == null) { 177 return null; 178 } 179 if (!fileEntryBlobs.isEmpty()) { 180 blob = fileEntryBlobs.get(0); 181 } 182 } 183 return new BatchFileEntry(fileEntryKey, blob); 184 } 185 } 186 187 /** 188 * Adds a file with the given {@code index} to the batch. 189 * 190 * @return The key of the new {@link BatchFileEntry}. 191 * @deprecated since 10.1, use the {@link Blob}-based signature instead 192 */ 193 @Deprecated 194 public String addFile(String index, InputStream is, String name, String mime) throws IOException { 195 Blob blob = Blobs.createBlob(is); 196 return addFile(index, blob, name, mime); 197 } 198 199 /** 200 * Adds a file with the given {@code index} to the batch. 201 * 202 * @return The key of the new {@link BatchFileEntry}. 203 * @since 10.1 204 */ 205 public String addFile(String index, Blob blob, String name, String mime) { 206 blob.setFilename(name); 207 blob.setMimeType(mime); 208 String fileEntryKey = key + "_" + index; 209 transientStore.putBlobs(fileEntryKey, Collections.singletonList(blob)); 210 transientStore.putParameter(fileEntryKey, CHUNKED_PARAM_NAME, String.valueOf(false)); 211 transientStore.putParameter(key, index, fileEntryKey); 212 return fileEntryKey; 213 } 214 215 /** 216 * Adds a chunk with the given {@code chunkIndex} to the batch file with the given {@code index}. 217 * 218 * @return The key of the {@link BatchFileEntry}. 219 * @since 7.4 220 * @deprecated since 10.1, use the {@link Blob}-based signature instead 221 */ 222 @Deprecated 223 public String addChunk(String index, InputStream is, int chunkCount, int chunkIndex, String fileName, 224 String mimeType, long fileSize) throws IOException { 225 Blob blob = Blobs.createBlob(is); 226 return addChunk(index, blob, chunkCount, chunkIndex, fileName, mimeType, fileSize); 227 } 228 229 /** 230 * Adds a chunk with the given {@code chunkIndex} to the batch file with the given {@code index}. 231 * 232 * @return The key of the {@link BatchFileEntry}. 233 * @since 10.1 234 */ 235 public String addChunk(String index, Blob blob, int chunkCount, int chunkIndex, String fileName, String mimeType, 236 long fileSize) { 237 String fileEntryKey = key + "_" + index; 238 BatchFileEntry fileEntry = getFileEntry(index); 239 if (fileEntry == null) { 240 fileEntry = new BatchFileEntry(fileEntryKey, chunkCount, fileName, mimeType, fileSize); 241 transientStore.putParameters(fileEntryKey, fileEntry.getParams()); 242 transientStore.putParameter(key, index, fileEntryKey); 243 } 244 fileEntry.addChunk(chunkIndex, blob); 245 return fileEntryKey; 246 } 247 248 /** 249 * @since 7.4 250 */ 251 public void clean() { 252 // Remove batch and all related storage entries from transient store, GC will clean up the files 253 log.debug(String.format("Cleaning batch %s", key)); 254 for (String fileIndex : fileEntries.keySet()) { 255 removeFileEntry(fileIndex, transientStore); 256 } 257 // Remove batch entry 258 transientStore.remove(key); 259 } 260 261 /** 262 * @since 8.4 263 */ 264 public boolean removeFileEntry(String index, TransientStore ts) { 265 // Check for chunk entries to remove 266 BatchFileEntry fileEntry = getFileEntry(index, false); 267 if (fileEntry == null) { 268 return false; 269 } 270 if (fileEntry.isChunked()) { 271 for (String chunkEntryKey : fileEntry.getChunkEntryKeys()) { 272 ts.remove(chunkEntryKey); 273 } 274 fileEntry.beforeRemove(); 275 } 276 String fileEntryKey = fileEntry.getKey(); 277 ts.remove(fileEntryKey); 278 return true; 279 } 280 281 /** 282 * @since 8.4 283 */ 284 public boolean removeFileEntry(String index) { 285 return removeFileEntry(index, transientStore); 286 } 287 288 /** 289 * @since 10.1 290 */ 291 public String getHandlerName() { 292 return handlerName; 293 } 294 295 /** 296 * @since 10.1 297 */ 298 public Map<String, Object> getProperties() { 299 return properties; 300 } 301 302}