/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Thierry Delprat <tdelprat@nuxeo.com> * Antoine Taillefer <ataillefer@nuxeo.com> * Gabriel Barata <gbarata@nuxeo.com> * */ package org.nuxeo.ecm.automation.server.jaxrs.batch; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.transientstore.api.TransientStore; import org.nuxeo.runtime.api.Framework; /** * Batch Object to encapsulate all data related to a batch, especially the temporary files used for Blobs. * <p> * Since 7.4 a batch is backed by the {@link TransientStore}. * * @since 5.4.2 */ public class Batch { protected static final Log log = LogFactory.getLog(Batch.class); public static final String CHUNKED_PARAM_NAME = "chunked"; protected String key; protected Map<String, Serializable> fileEntries; public Batch(String key) { this(key, new HashMap<>()); } public Batch(String key, Map<String, Serializable> fileEntries) { this.key = key; this.fileEntries = fileEntries; } public String getKey() { return key; } /** * Returns the uploaded blobs in the order the user chose to upload them. */ public List<Blob> getBlobs() { List<Blob> blobs = new ArrayList<Blob>(); List<String> sortedFileIndexes = getOrderedFileIndexes(); log.debug(String.format("Retrieving blobs for batch %s: %s", key, sortedFileIndexes)); for (String index : sortedFileIndexes) { Blob blob = retrieveBlob(index); if (blob != null) { blobs.add(blob); } } return blobs; } public Blob getBlob(String index) { log.debug(String.format("Retrieving blob %s for batch %s", index, key)); return retrieveBlob(index); } protected List<String> getOrderedFileIndexes() { List<String> sortedFileIndexes = new ArrayList<String>(fileEntries.keySet()); Collections.sort(sortedFileIndexes, new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.valueOf(o1).compareTo(Integer.valueOf(o2)); } }); return sortedFileIndexes; } protected Blob retrieveBlob(String index) { Blob blob = null; BatchFileEntry fileEntry = getFileEntry(index); if (fileEntry != null) { blob = fileEntry.getBlob(); } return blob; } public List<BatchFileEntry> getFileEntries() { List<BatchFileEntry> batchFileEntries = new ArrayList<BatchFileEntry>(); List<String> sortedFileIndexes = getOrderedFileIndexes(); for (String index : sortedFileIndexes) { BatchFileEntry fileEntry = getFileEntry(index); if (fileEntry != null) { batchFileEntries.add(fileEntry); } } return batchFileEntries; } public BatchFileEntry getFileEntry(String index) { return getFileEntry(index, true); } public BatchFileEntry getFileEntry(String index, boolean fetchBlobs) { BatchManager bm = Framework.getService(BatchManager.class); String fileEntryKey = (String) fileEntries.get(index); if (fileEntryKey == null) { return null; } TransientStore ts = bm.getTransientStore(); Map<String, Serializable> fileEntryParams = ts.getParameters(fileEntryKey); if (fileEntryParams == null) { return null; } boolean chunked = Boolean.parseBoolean((String) fileEntryParams.get(CHUNKED_PARAM_NAME)); if (chunked) { return new BatchFileEntry(fileEntryKey, fileEntryParams); } else { Blob blob = null; if (fetchBlobs) { List<Blob> fileEntryBlobs = ts.getBlobs(fileEntryKey); if (fileEntryBlobs == null) { return null; } if (!fileEntryBlobs.isEmpty()) { blob = fileEntryBlobs.get(0); } } return new BatchFileEntry(fileEntryKey, blob); } } /** * Adds a file with the given {@code index} to the batch. * * @return The key of the new {@link BatchFileEntry}. */ public String addFile(String index, InputStream is, String name, String mime) throws IOException { String mimeType = mime; if (mimeType == null) { mimeType = "application/octet-stream"; } Blob blob = Blobs.createBlob(is, mime); blob.setFilename(name); String fileEntryKey = key + "_" + index; BatchManager bm = Framework.getService(BatchManager.class); TransientStore ts = bm.getTransientStore(); ts.putBlobs(fileEntryKey, Collections.singletonList(blob)); ts.putParameter(fileEntryKey, CHUNKED_PARAM_NAME, String.valueOf(false)); ts.putParameter(key, index, fileEntryKey); return fileEntryKey; } /** * Adds a chunk with the given {@code chunkIndex} to the batch file with the given {@code index}. * * @return The key of the {@link BatchFileEntry}. * @since 7.4 */ public String addChunk(String index, InputStream is, int chunkCount, int chunkIndex, String fileName, String mimeType, long fileSize) throws IOException { BatchManager bm = Framework.getService(BatchManager.class); Blob blob = Blobs.createBlob(is); String fileEntryKey = key + "_" + index; BatchFileEntry fileEntry = getFileEntry(index); if (fileEntry == null) { fileEntry = new BatchFileEntry(fileEntryKey, chunkCount, fileName, mimeType, fileSize); TransientStore ts = bm.getTransientStore(); ts.putParameters(fileEntryKey, fileEntry.getParams()); ts.putParameter(key, index, fileEntryKey); } fileEntry.addChunk(chunkIndex, blob); return fileEntryKey; } /** * @since 7.4 */ public void clean() { // Remove batch and all related storage entries from transient store, GC will clean up the files log.debug(String.format("Cleaning batch %s", key)); BatchManager bm = Framework.getService(BatchManager.class); TransientStore ts = bm.getTransientStore(); for (String fileIndex : fileEntries.keySet()) { removeFileEntry(fileIndex, ts); } // Remove batch entry ts.remove(key); } /** * @since 8.4 */ public boolean removeFileEntry(String index, TransientStore ts) { // Check for chunk entries to remove BatchFileEntry fileEntry = getFileEntry(index, false); if (fileEntry == null) { return false; } if (fileEntry.isChunked()) { for (String chunkEntryKey : fileEntry.getChunkEntryKeys()) { // Remove chunk entry from the store and delete blobs from the file system List<Blob> chunkBlobs = ts.getBlobs(chunkEntryKey); if (chunkBlobs != null) { for (Blob blob : chunkBlobs) { FileUtils.deleteQuietly(blob.getFile().getParentFile()); } } ts.remove(chunkEntryKey); } fileEntry.beforeRemove(); } // Remove file entry from the store and delete blobs from the file system String fileEntryKey = fileEntry.getKey(); List<Blob> fileBlobs = ts.getBlobs(fileEntryKey); if (fileBlobs != null) { for (Blob blob : fileBlobs) { FileUtils.deleteQuietly(blob.getFile().getParentFile()); } } ts.remove(fileEntryKey); return true; } /** * @since 8.4 */ public boolean removeFileEntry(String index) { BatchManager bm = Framework.getService(BatchManager.class); TransientStore ts = bm.getTransientStore(); return removeFileEntry(index, ts); } }