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