001/*
002 * (C) Copyright 2017 Nuxeo (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 *     Guillaume Renard <grenard@nuxeo.com>
018 */
019package org.nuxeo.ecm.automation.core.work;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.ecm.core.api.Blob;
030import org.nuxeo.ecm.core.api.DocumentModel;
031import org.nuxeo.ecm.core.api.DocumentRef;
032import org.nuxeo.ecm.core.api.IdRef;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.core.api.impl.blob.AsyncBlob;
035import org.nuxeo.ecm.core.io.download.DownloadService;
036import org.nuxeo.ecm.core.transientstore.api.TransientStore;
037import org.nuxeo.ecm.core.transientstore.api.TransientStoreService;
038import org.nuxeo.ecm.core.transientstore.work.TransientStoreWork;
039import org.nuxeo.ecm.core.utils.BlobUtils;
040import org.nuxeo.runtime.api.Framework;
041
042/**
043 * Work to zip a list of document default blob and store the produced zip into the TransientStore.
044 *
045 * @since 9.3
046 */
047public class BlobListZipWork extends TransientStoreWork {
048
049    private static final Log log = LogFactory.getLog(BlobListZipWork.class);
050
051    public static final String CATEGORY = "blobListZip";
052
053    public static final String CACHE_NAME = "blobListZip";
054
055    private static final long serialVersionUID = 1L;
056
057    protected List<String> docIds;
058
059    protected String filename;
060
061    protected final String key;
062
063    protected final String storeName;
064
065    public BlobListZipWork(String transientStoreKey, String originatingUsername, String filename, List<String> docIds) {
066        this(transientStoreKey, originatingUsername, filename, docIds, filename);
067    }
068
069    public BlobListZipWork(String transientStoreKey, String originatingUsername, String filename, List<String> docIds,
070            String storeName) {
071        this.key = transientStoreKey;
072        this.docIds = docIds;
073        this.originatingUsername = originatingUsername;
074        this.id = "BlobListZipWork-" + this.key + "-" + this.originatingUsername;
075        this.storeName = storeName;
076        if (StringUtils.isNotBlank(filename)) {
077            this.filename = filename.toLowerCase().endsWith(".zip") ? filename : filename + ".zip";
078        }
079
080    }
081
082    @Override
083    public void cleanUp(boolean ok, Exception e) {
084        super.cleanUp(ok, e);
085        if (ok) {
086            return;
087        }
088        List<Blob> blobs = Collections.singletonList(new AsyncBlob(key));
089        TransientStore ts = getTransientStore();
090        ts.putParameter(key, DownloadService.TRANSIENT_STORE_PARAM_ERROR, e.getMessage());
091        updateAndCompleteStoreEntry(blobs);
092    }
093
094    @Override
095    public String getCategory() {
096        return CATEGORY;
097    }
098
099    @Override
100    public String getTitle() {
101        return id;
102    }
103
104    public TransientStore getTransientStore() {
105        TransientStoreService tss = Framework.getService(TransientStoreService.class);
106        return tss.getStore(storeName != null ? storeName : CACHE_NAME);
107    }
108
109    protected void updateAndCompleteStoreEntry(List<Blob> blobs) {
110        TransientStore ts = getTransientStore();
111        if (!ts.exists(key)) {
112            throw new NuxeoException("Zip TransientStore entry can not be null");
113        }
114        ts.putBlobs(key, blobs);
115        ts.putParameter(key, DownloadService.TRANSIENT_STORE_PARAM_PROGRESS, 100);
116        ts.setCompleted(key, true);
117    }
118
119    @Override
120    public void work() {
121        openUserSession();
122        List<Blob> blobList = new ArrayList<>();
123        DownloadService downloadService = Framework.getService(DownloadService.class);
124        for (String docId : docIds) {
125            DocumentRef docRef = new IdRef(docId);
126            if (!session.exists(docRef)) {
127                if (log.isDebugEnabled()) {
128                    log.debug(String.format("Cannot retrieve document '%s', probably deleted in the meanwhile", docId));
129                }
130                continue;
131            }
132            DocumentModel doc = session.getDocument(docRef);
133            Blob blob = downloadService.resolveBlob(doc);
134            if (blob == null) {
135                log.trace("Not able to resolve blob");
136                continue;
137            } else if (!downloadService.checkPermission(doc, null, blob, "download", Collections.emptyMap())) {
138                if (log.isDebugEnabled()) {
139                    log.debug(
140                            String.format("Not allowed to bulk download blob for document %s", doc.getPathAsString()));
141                }
142                continue;
143            }
144            blobList.add(blob);
145        }
146        if (blobList.isEmpty()) {
147            log.debug("No blob to be zipped");
148            updateAndCompleteStoreEntry(Collections.emptyList());
149            return;
150        }
151        Blob blob;
152        String finalFilename = StringUtils.isNotBlank(this.filename) ? this.filename : this.id;
153        if (blobList.size() == 1) {
154            blob = blobList.get(0);
155            blob.setFilename(finalFilename);
156        } else {
157            try {
158                blob = BlobUtils.zip(blobList, finalFilename);
159            } catch (IOException e) {
160                TransientStore ts = getTransientStore();
161                ts.putParameter(key, DownloadService.TRANSIENT_STORE_PARAM_ERROR, e.getMessage());
162                throw new NuxeoException("Exception while zipping blob list", e);
163            }
164        }
165        updateAndCompleteStoreEntry(Collections.singletonList(blob));
166    }
167
168}