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