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}