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