001/*
002 * (C) Copyright 2006-2020 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 *     Tiago Cardoso <tcardoso@nuxeo.com>
018 */
019package org.nuxeo.ecm.platform.threed.service;
020
021import static org.nuxeo.ecm.platform.threed.ThreeDConstants.THREED_TYPE;
022import static org.nuxeo.ecm.platform.threed.ThreeDDocumentConstants.MAIN_INFO_PROPERTY;
023import static org.nuxeo.ecm.platform.threed.ThreeDDocumentConstants.RENDER_VIEWS_PROPERTY;
024import static org.nuxeo.ecm.platform.threed.ThreeDDocumentConstants.TRANSMISSIONS_PROPERTY;
025
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.Map;
030import java.util.stream.Collectors;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.nuxeo.ecm.core.api.CoreSession;
035import org.nuxeo.ecm.core.api.DocumentModel;
036import org.nuxeo.ecm.core.api.IdRef;
037import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
038import org.nuxeo.ecm.core.convert.api.ConverterNotRegistered;
039import org.nuxeo.ecm.core.event.Event;
040import org.nuxeo.ecm.core.event.EventService;
041import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
042import org.nuxeo.ecm.core.work.AbstractWork;
043import org.nuxeo.ecm.platform.filemanager.core.listener.MimetypeIconUpdater;
044import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry;
045import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
046import org.nuxeo.ecm.platform.threed.BatchConverterHelper;
047import org.nuxeo.ecm.platform.threed.ThreeD;
048import org.nuxeo.ecm.platform.threed.ThreeDDocument;
049import org.nuxeo.ecm.platform.threed.ThreeDInfo;
050import org.nuxeo.ecm.platform.threed.ThreeDRenderView;
051import org.nuxeo.ecm.platform.threed.TransmissionThreeD;
052import org.nuxeo.runtime.api.Framework;
053
054/**
055 * Work running batch conversions to update 3D document type preview assets
056 *
057 * @since 8.4
058 */
059public class ThreeDBatchUpdateWork extends AbstractWork {
060
061    private static final long serialVersionUID = 1L;
062
063    private static final Log log = LogFactory.getLog(ThreeDBatchUpdateWork.class);
064
065    public static final String CATEGORY_THREED_CONVERSION = "threeDConversion";
066
067    public static final String THREED_CONVERSIONS_DONE_EVENT = "threeDConversionsDone";
068
069    // @since 11.1
070    protected static final String STATUS_DONE = "Done";
071
072    protected static String computeIdPrefix(String repositoryName, String docId) {
073        return repositoryName + ':' + docId + ":threedbatch:";
074    }
075
076    public ThreeDBatchUpdateWork(String repositoryName, String docId) {
077        super(computeIdPrefix(repositoryName, docId));
078        setDocument(repositoryName, docId);
079    }
080
081    @Override
082    public String getCategory() {
083        return CATEGORY_THREED_CONVERSION;
084    }
085
086    @Override
087    public String getTitle() {
088        return "3D preview batch update";
089    }
090
091    @Override
092    public boolean isIdempotent() {
093        return false;
094    }
095
096    @Override
097    public boolean isGroupJoin() {
098        // This is a GroupJoin work with a trigger that can be used on the last work execution
099        return true;
100    }
101
102    @Override
103    public String getPartitionKey() {
104        return computeIdPrefix(repositoryName, docId);
105    }
106
107    @Override
108    public void onGroupJoinCompletion() {
109        if (STATUS_DONE.equals(getStatus())) {
110            try {
111                openSystemSession();
112                fireThreeDConversionsDoneEvent(session.getDocument(new IdRef(docId)));
113            } finally {
114                cleanUp(true, null);
115            }
116        }
117    }
118
119    @Override
120    public void work() {
121        if (isWorkInstanceSuspended()) {
122            return;
123        }
124        // Extract
125        setStatus("Extracting");
126        setProgress(Progress.PROGRESS_INDETERMINATE);
127
128        ThreeD originalThreeD = null;
129        try {
130            openSystemSession();
131            DocumentModel doc = session.getDocument(new IdRef(docId));
132            originalThreeD = getThreeDToConvert(doc);
133            commitOrRollbackTransaction();
134        } finally {
135            cleanUp(true, null);
136        }
137
138        if (originalThreeD == null) {
139            setStatus("Nothing to process");
140            return;
141        }
142
143        if (isWorkInstanceSuspended()) {
144            return;
145        }
146        // Perform batch conversion
147        setStatus("Batch conversion");
148        ThreeDService service = Framework.getService(ThreeDService.class);
149        BlobHolder batch;
150        try {
151            batch = service.batchConvert(originalThreeD);
152        } catch (ConverterNotRegistered e) {
153            return;
154        }
155
156        if (isWorkInstanceSuspended()) {
157            return;
158        }
159        // Saving thumbnail to the document
160        setStatus("Saving thumbnail");
161        List<ThreeDRenderView> threeDRenderViews = BatchConverterHelper.getRenders(batch);
162        long numRenderViews = service.getAutomaticRenderViews().stream().filter(RenderView::isEnabled).count();
163        if (!threeDRenderViews.isEmpty() && threeDRenderViews.size() == numRenderViews) {
164            try {
165                startTransaction();
166                openSystemSession();
167                DocumentModel doc = session.getDocument(new IdRef(docId));
168                saveNewRenderViews(doc, threeDRenderViews);
169                commitOrRollbackTransaction();
170            } finally {
171                cleanUp(true, null);
172            }
173        }
174
175        if (isWorkInstanceSuspended()) {
176            return;
177        }
178        // Save 3D blob information
179        setStatus("Saving 3D information on main content");
180        List<BlobHolder> resources = BatchConverterHelper.getResources(batch);
181        ThreeDInfo mainInfo;
182        mainInfo = BatchConverterHelper.getMainInfo(batch, resources);
183        if (mainInfo != null) {
184            try {
185                startTransaction();
186                openSystemSession();
187                DocumentModel doc = session.getDocument(new IdRef(docId));
188                saveMainInfo(doc, mainInfo);
189                commitOrRollbackTransaction();
190            } finally {
191                cleanUp(true, null);
192            }
193        }
194
195        if (isWorkInstanceSuspended()) {
196            return;
197        }
198        // Convert transmission formats
199        setStatus("Converting Collada to glTF");
200
201        List<TransmissionThreeD> colladaThreeDs = BatchConverterHelper.getTransmissions(batch, resources);
202        List<TransmissionThreeD> transmissionThreeDs = colladaThreeDs.stream()
203                                                                     .map(service::convertColladaToglTF)
204                                                                     .collect(Collectors.toList());
205
206        if (isWorkInstanceSuspended()) {
207            return;
208        }
209        // Save transmission formats
210        setStatus("Saving transmission formats");
211        startTransaction();
212        openSystemSession();
213        DocumentModel doc = session.getDocument(new IdRef(docId));
214        saveNewTransmissionThreeDs(doc, transmissionThreeDs);
215
216        if (isWorkInstanceSuspended()) {
217            return;
218        }
219        // Finalize
220        setStatus(STATUS_DONE);
221    }
222
223    protected ThreeD getThreeDToConvert(DocumentModel doc) {
224        ThreeDDocument threedDocument = doc.getAdapter(ThreeDDocument.class);
225        ThreeD threed = threedDocument.getThreeD();
226        if (threed == null) {
227            log.warn("No original 3d to process for: " + doc);
228        }
229        return threed;
230    }
231
232    protected void saveNewProperties(DocumentModel doc, Serializable properties, String schema) {
233        doc.setPropertyValue(schema, properties);
234        if (doc.isVersion()) {
235            doc.putContextData(CoreSession.ALLOW_VERSION_WRITE, Boolean.TRUE);
236        }
237        session.saveDocument(doc);
238    }
239
240    protected void saveMainInfo(DocumentModel doc, ThreeDInfo info) {
241        saveNewProperties(doc, (Serializable) info.toMap(), MAIN_INFO_PROPERTY);
242
243    }
244
245    protected void saveNewTransmissionThreeDs(DocumentModel doc, List<TransmissionThreeD> transmissionThreeDs) {
246        List<Map<String, Serializable>> transmissionList = new ArrayList<>();
247        transmissionList.addAll(
248                transmissionThreeDs.stream().map(TransmissionThreeD::toMap).collect(Collectors.toList()));
249        saveNewProperties(doc, (Serializable) transmissionList, TRANSMISSIONS_PROPERTY);
250    }
251
252    protected void saveNewRenderViews(DocumentModel doc, List<ThreeDRenderView> threeDRenderViews) {
253        List<Map<String, Serializable>> renderViewList = new ArrayList<>();
254        renderViewList.addAll(threeDRenderViews.stream().map(ThreeDRenderView::toMap).collect(Collectors.toList()));
255
256        saveNewProperties(doc, (Serializable) renderViewList, RENDER_VIEWS_PROPERTY);
257    }
258
259    /**
260     * Fire a {@code THREED_CONVERSIONS_DONE_EVENT}
261     */
262    protected void fireThreeDConversionsDoneEvent(DocumentModel doc) {
263        log.debug("Fire threeDConversionsDone event for doc: " + doc.getId());
264        DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), doc);
265        Event event = ctx.newEvent(THREED_CONVERSIONS_DONE_EVENT);
266        Framework.getService(EventService.class).fireEvent(event);
267        // force the 3d doc icon
268        MimetypeIconUpdater iconUpdater = new MimetypeIconUpdater();
269        MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
270        MimetypeEntry mimeTypeEntry = mimetypeRegistry.getMimetypeEntryByMimeType(THREED_TYPE);
271        iconUpdater.updateIconField(mimeTypeEntry, doc);
272    }
273
274}