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}