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.operations.blob; 020 021import java.io.IOException; 022import java.util.Calendar; 023import java.util.Collections; 024import java.util.List; 025import java.util.stream.Collectors; 026 027import org.apache.commons.codec.digest.DigestUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.ecm.automation.core.Constants; 031import org.nuxeo.ecm.automation.core.annotations.Context; 032import org.nuxeo.ecm.automation.core.annotations.Operation; 033import org.nuxeo.ecm.automation.core.annotations.OperationMethod; 034import org.nuxeo.ecm.automation.core.annotations.Param; 035import org.nuxeo.ecm.automation.core.work.BlobListZipWork; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.CoreSession; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.DocumentModelList; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.blobholder.BlobHolderAdapterService; 042import org.nuxeo.ecm.core.api.impl.blob.AsyncBlob; 043import org.nuxeo.ecm.core.io.download.DownloadService; 044import org.nuxeo.ecm.core.transientstore.api.TransientStore; 045import org.nuxeo.ecm.core.transientstore.api.TransientStoreService; 046import org.nuxeo.ecm.core.work.api.Work; 047import org.nuxeo.ecm.core.work.api.WorkManager; 048import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling; 049import org.nuxeo.runtime.api.Framework; 050 051/** 052 * Asynchronous Bulk Download Operation. 053 * 054 * @since 9.3 055 */ 056@Operation(id = BulkDownload.ID, category = Constants.CAT_BLOB, label = "Bulk Downlaod", description = "Prepare a Zip of a list of documents which is build asynchrously. Produced Zip will be available in the TransientStore with the key returned by the JSON.") 057public class BulkDownload { 058 059 private static final Log log = LogFactory.getLog(BulkDownload.class); 060 061 public static final String ID = "Blob.BulkDownload"; 062 063 public static final String WORKERID_KEY = "workerid"; 064 065 @Context 066 protected CoreSession session; 067 068 @Context 069 BlobHolderAdapterService blobHolderAdapterService; 070 071 @Param(name = "filename", required = false) 072 protected String fileName; 073 074 protected String buildTransientStoreKey(DocumentModelList docs) { 075 StringBuffer sb = new StringBuffer(); 076 for (DocumentModel doc : docs) { 077 sb.append(doc.getId()); 078 sb.append("::"); 079 Calendar modif = (Calendar) doc.getPropertyValue("dc:modified"); 080 if (modif != null) { 081 long millis = modif.getTimeInMillis(); 082 // the date may have been rounded by the storage layer, normalize it to the second 083 millis -= millis % 1000; 084 sb.append(millis); 085 sb.append("::"); 086 } 087 } 088 // Rendered documents might differ according to current user 089 sb.append(session.getPrincipal().getName()); 090 return DigestUtils.md5Hex(sb.toString()); 091 } 092 093 @OperationMethod 094 public Blob run(DocumentModelList docs) throws IOException { 095 // build the key 096 String key = buildTransientStoreKey(docs); 097 TransientStoreService tss = Framework.getService(TransientStoreService.class); 098 099 TransientStore ts = tss.getStore(DownloadService.TRANSIENT_STORE_STORE_NAME); 100 if (ts == null) { 101 throw new NuxeoException("Unable to find download Transient Store"); 102 } 103 List<Blob> blobs = null; 104 if (!ts.exists(key)) { 105 log.trace("No async download already initialized"); 106 Work work = new BlobListZipWork(key, session.getPrincipal().getName(), this.fileName, 107 docs.stream().map(DocumentModel::getId).collect(Collectors.toList()), 108 DownloadService.TRANSIENT_STORE_STORE_NAME); 109 ts.setCompleted(key, false); 110 ts.putParameter(key, WORKERID_KEY, work.getId()); 111 blobs = Collections.singletonList(new AsyncBlob(key)); 112 ts.putBlobs(key, blobs); 113 Framework.getService(WorkManager.class).schedule(work, Scheduling.IF_NOT_SCHEDULED); 114 return blobs.get(0); 115 } else { 116 log.trace("Async download already initialized"); 117 blobs = ts.getBlobs(key); 118 if (ts.isCompleted(key)) { 119 if (blobs != null && blobs.size() == 1) { 120 Blob blob = blobs.get(0); 121 ts.release(key); 122 return blob; 123 } else { 124 ts.release(key); 125 throw new NuxeoException("Cannot retrieve blob"); 126 } 127 128 } else { 129 Work work = new BlobListZipWork(key, session.getPrincipal().getName(), this.fileName, 130 docs.stream().map(DocumentModel::getId).collect(Collectors.toList()), 131 DownloadService.TRANSIENT_STORE_STORE_NAME); 132 WorkManager wm = Framework.getService(WorkManager.class); 133 wm.schedule(work, Scheduling.IF_NOT_SCHEDULED); 134 return new AsyncBlob(key); 135 } 136 } 137 } 138 139}