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