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;
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}