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.List; 028import java.util.Map; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.ecm.core.api.Blob; 033import org.nuxeo.ecm.core.api.Blobs; 034import org.nuxeo.ecm.core.transientstore.AbstractStorageEntry; 035import org.nuxeo.ecm.core.transientstore.api.TransientStore; 036import org.nuxeo.runtime.api.Framework; 037 038/** 039 * Batch Object to encapsulate all data related to a batch, especially the temporary files used for Blobs. 040 * <p> 041 * Since 7.4 a batch is backed by the {@link TransientStore}. 042 * 043 * @since 5.4.2 044 */ 045public class Batch extends AbstractStorageEntry { 046 047 private static final long serialVersionUID = 1L; 048 049 protected static final Log log = LogFactory.getLog(Batch.class); 050 051 public Batch(String id) { 052 super(id); 053 } 054 055 /** 056 * Returns the uploaded blobs in the order the user chose to upload them. 057 */ 058 @Override 059 public List<Blob> getBlobs() { 060 List<Blob> blobs = new ArrayList<Blob>(); 061 List<String> sortedFileIndexes = getOrderedFileIndexes(); 062 log.debug(String.format("Retrieving blobs for batch %s: %s", getId(), sortedFileIndexes)); 063 for (String idx : sortedFileIndexes) { 064 Blob blob = retrieveBlob(idx); 065 if (blob != null) { 066 blobs.add(blob); 067 } 068 } 069 return blobs; 070 } 071 072 public Blob getBlob(String idx) { 073 log.debug(String.format("Retrieving blob %s for batch %s", idx, getId())); 074 return retrieveBlob(idx); 075 } 076 077 protected List<String> getOrderedFileIndexes() { 078 List<String> sortedFileIndexes = new ArrayList<String>(); 079 if (getParameters() != null) { 080 sortedFileIndexes = new ArrayList<String>(getParameters().keySet()); 081 Collections.sort(sortedFileIndexes, new Comparator<String>() { 082 @Override 083 public int compare(String o1, String o2) { 084 return Integer.valueOf(o1).compareTo(Integer.valueOf(o2)); 085 } 086 }); 087 } 088 return sortedFileIndexes; 089 } 090 091 protected Blob retrieveBlob(String idx) { 092 Blob blob = null; 093 BatchFileEntry fileEntry = getFileEntry(idx); 094 if (fileEntry != null) { 095 blob = fileEntry.getBlob(); 096 } 097 return blob; 098 } 099 100 public List<BatchFileEntry> getFileEntries() { 101 List<BatchFileEntry> fileEntries = new ArrayList<BatchFileEntry>(); 102 List<String> sortedFileIndexes = getOrderedFileIndexes(); 103 for (String idx : sortedFileIndexes) { 104 BatchFileEntry fileEntry = getFileEntry(idx); 105 if (fileEntry != null) { 106 fileEntries.add(fileEntry); 107 } 108 } 109 return fileEntries; 110 } 111 112 public BatchFileEntry getFileEntry(String idx) { 113 BatchManager bm = Framework.getService(BatchManager.class); 114 String fileEntryId = (String) get(idx); 115 if (fileEntryId == null) { 116 return null; 117 } 118 return (BatchFileEntry) bm.getTransientStore().get(fileEntryId); 119 } 120 121 /** 122 * Adds a file with the given {@code idx} to the batch. 123 * 124 * @return The id of the new {@link BatchFileEntry}. 125 */ 126 public String addFile(String idx, InputStream is, String name, String mime) throws IOException { 127 String mimeType = mime; 128 if (mimeType == null) { 129 mimeType = "application/octet-stream"; 130 } 131 Blob blob = Blobs.createBlob(is, mime); 132 blob.setFilename(name); 133 134 String fileEntryId = getId() + "_" + idx; 135 BatchFileEntry fileEntry = new BatchFileEntry(fileEntryId, blob); 136 137 BatchManager bm = Framework.getService(BatchManager.class); 138 bm.getTransientStore().put(fileEntry); 139 140 return fileEntryId; 141 } 142 143 /** 144 * Adds a chunk with the given {@code chunkIdx} to the batch file with the given {@code idx}. 145 * 146 * @return The id of the {@link BatchFileEntry}. 147 * @since 7.4 148 */ 149 public String addChunk(String idx, InputStream is, int chunkCount, int chunkIdx, String name, String mime, 150 long fileSize) throws IOException { 151 BatchManager bm = Framework.getService(BatchManager.class); 152 Blob blob = Blobs.createBlob(is); 153 154 BatchFileEntry fileEntry = null; 155 String fileEntryId = (String) get(idx); 156 if (fileEntryId != null) { 157 fileEntry = (BatchFileEntry) bm.getTransientStore().get(fileEntryId); 158 } 159 if (fileEntry == null) { 160 if (fileEntryId == null) { 161 fileEntryId = getId() + "_" + idx; 162 } 163 fileEntry = new BatchFileEntry(fileEntryId, chunkCount, name, mime, fileSize); 164 bm.getTransientStore().put(fileEntry); 165 } 166 String chunkEntryId = fileEntry.addChunk(chunkIdx, blob); 167 168 // Need to synchronize manipulation of the file TransientStore entry params 169 synchronized (this) { 170 fileEntry = (BatchFileEntry) bm.getTransientStore().get(fileEntryId); 171 fileEntry.getChunks().put(chunkIdx, chunkEntryId); 172 put(idx, fileEntryId); 173 bm.getTransientStore().put(fileEntry); 174 } 175 return fileEntryId; 176 } 177 178 /** 179 * @since 7.4 180 */ 181 public void clean() { 182 // Remove batch and all related storage entries from transient store, GC will clean up the files 183 log.debug(String.format("Cleaning batch %s", getId())); 184 BatchManager bm = Framework.getService(BatchManager.class); 185 TransientStore ts = bm.getTransientStore(); 186 Map<String, Serializable> params = getParameters(); 187 if (params != null) { 188 for (Serializable v : params.values()) { 189 String fileEntryId = (String) v; 190 // Check for chunk entries to remove 191 BatchFileEntry fileEntry = (BatchFileEntry) ts.get(fileEntryId); 192 if (fileEntry != null) { 193 if (fileEntry.isChunked()) { 194 for (String chunkEntryId : fileEntry.getChunkEntryIds()) { 195 // Remove chunk entry 196 ts.remove(chunkEntryId); 197 } 198 } 199 // Remove file entry 200 ts.remove(fileEntryId); 201 } 202 } 203 } 204 // Remove batch entry 205 ts.remove(getId()); 206 } 207 208 @Override 209 public void beforeRemove() { 210 // Nothing to do here 211 } 212 213}