001/*
002 * (C) Copyright 2010-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 *   Nuxeo - initial API and implementation
018 */
019package org.nuxeo.ecm.platform.rendition.service;
020
021import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_ID_PROPERTY;
022import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_MODIFICATION_DATE_PROPERTY;
023import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Calendar;
028import java.util.Collections;
029import java.util.Deque;
030import java.util.HashMap;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034import java.util.Objects;
035import java.util.stream.Collectors;
036
037import javax.script.Invocable;
038import javax.script.ScriptContext;
039import javax.script.ScriptEngine;
040import javax.script.ScriptEngineManager;
041import javax.script.ScriptException;
042
043import org.apache.commons.lang.StringUtils;
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046import org.nuxeo.ecm.automation.AutomationService;
047import org.nuxeo.ecm.core.api.Blob;
048import org.nuxeo.ecm.core.api.CoreSession;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.core.api.DocumentRef;
051import org.nuxeo.ecm.core.api.IdRef;
052import org.nuxeo.ecm.core.api.IterableQueryResult;
053import org.nuxeo.ecm.core.api.NuxeoException;
054import org.nuxeo.ecm.core.api.NuxeoPrincipal;
055import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
056import org.nuxeo.ecm.core.api.VersioningOption;
057import org.nuxeo.ecm.core.api.local.ClientLoginModule;
058import org.nuxeo.ecm.core.query.sql.NXQL;
059import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder;
060import org.nuxeo.ecm.platform.rendition.Rendition;
061import org.nuxeo.ecm.platform.rendition.extension.DefaultAutomationRenditionProvider;
062import org.nuxeo.ecm.platform.rendition.extension.RenditionProvider;
063import org.nuxeo.ecm.platform.rendition.impl.LazyRendition;
064import org.nuxeo.ecm.platform.rendition.impl.LiveRendition;
065import org.nuxeo.ecm.platform.rendition.impl.StoredRendition;
066import org.nuxeo.runtime.api.Framework;
067import org.nuxeo.runtime.model.ComponentContext;
068import org.nuxeo.runtime.model.ComponentInstance;
069import org.nuxeo.runtime.model.DefaultComponent;
070import org.nuxeo.runtime.transaction.TransactionHelper;
071
072/**
073 * Default implementation of {@link RenditionService}.
074 *
075 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
076 * @since 5.4.1
077 */
078public class RenditionServiceImpl extends DefaultComponent implements RenditionService {
079
080    public static final String RENDITION_DEFINITIONS_EP = "renditionDefinitions";
081
082    public static final String RENDITON_DEFINION_PROVIDERS_EP = "renditionDefinitionProviders";
083
084    public static final String DEFAULT_RENDITION_EP = "defaultRendition";
085
086    /**
087     * @since 8.1
088     */
089    public static final String STORED_RENDITION_MANAGERS_EP = "storedRenditionManagers";
090
091    private static final Log log = LogFactory.getLog(RenditionServiceImpl.class);
092
093    /**
094     * @deprecated since 7.2. Not used.
095     */
096    @Deprecated
097    protected AutomationService automationService;
098
099    /**
100     * @deprecated since 7.3.
101     */
102    @Deprecated
103    protected Map<String, RenditionDefinition> renditionDefinitions;
104
105    /**
106     * @since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
107     */
108    protected RenditionDefinitionRegistry renditionDefinitionRegistry;
109
110    protected RenditionDefinitionProviderRegistry renditionDefinitionProviderRegistry;
111
112    protected List<DefaultRenditionDescriptor> defaultRenditionDescriptors = new ArrayList<>();;
113
114    protected DefaultRenditionDescriptor defaultRenditionDescriptor;
115
116    protected static final StoredRenditionManager DEFAULT_STORED_RENDITION_MANAGER = new DefaultStoredRenditionManager();
117
118    /**
119     * @since 8.1
120     */
121    protected Deque<StoredRenditionManagerDescriptor> storedRenditionManagerDescriptors = new LinkedList<>();
122
123    protected final ScriptEngineManager scriptEngineManager;
124
125    public RenditionServiceImpl() {
126        scriptEngineManager = new ScriptEngineManager();
127    }
128
129    /**
130     * @since 8.1
131     */
132    public StoredRenditionManager getStoredRenditionManager() {
133        StoredRenditionManagerDescriptor descr = storedRenditionManagerDescriptors.peekLast();
134        return descr == null ? DEFAULT_STORED_RENDITION_MANAGER : descr.getStoredRenditionManager();
135    }
136
137    @Override
138    public void activate(ComponentContext context) {
139        renditionDefinitions = new HashMap<>();
140        renditionDefinitionRegistry = new RenditionDefinitionRegistry();
141        renditionDefinitionProviderRegistry = new RenditionDefinitionProviderRegistry();
142        super.activate(context);
143    }
144
145    @Override
146    public void deactivate(ComponentContext context) {
147        renditionDefinitions = null;
148        renditionDefinitionRegistry = null;
149        renditionDefinitionProviderRegistry = null;
150        super.deactivate(context);
151    }
152
153    public RenditionDefinition getRenditionDefinition(String name) {
154        return renditionDefinitionRegistry.getRenditionDefinition(name);
155    }
156
157    /**
158     * @deprecated since 7.2 because unused
159     */
160    @Override
161    @Deprecated
162    public List<RenditionDefinition> getDeclaredRenditionDefinitions() {
163        return new ArrayList<>(renditionDefinitionRegistry.descriptors.values());
164    }
165
166    /**
167     * @deprecated since 7.2 because unused
168     */
169    @Override
170    @Deprecated
171    public List<RenditionDefinition> getDeclaredRenditionDefinitionsForProviderType(String providerType) {
172        List<RenditionDefinition> defs = new ArrayList<>();
173        for (RenditionDefinition def : getDeclaredRenditionDefinitions()) {
174            if (def.getProviderType().equals(providerType)) {
175                defs.add(def);
176            }
177        }
178        return defs;
179    }
180
181    @Override
182    public List<RenditionDefinition> getAvailableRenditionDefinitions(DocumentModel doc) {
183
184        List<RenditionDefinition> defs = new ArrayList<>();
185        defs.addAll(renditionDefinitionRegistry.getRenditionDefinitions(doc));
186        defs.addAll(renditionDefinitionProviderRegistry.getRenditionDefinitions(doc));
187
188        // XXX what about "lost renditions" ?
189        return defs;
190    }
191
192    @Override
193    public DocumentRef storeRendition(DocumentModel source, String renditionDefinitionName) {
194        Rendition rendition = getRendition(source, renditionDefinitionName, true);
195        return rendition == null ? null : rendition.getHostDocument().getRef();
196    }
197
198    /**
199     * @deprecated since 8.1
200     */
201    @Deprecated
202    protected DocumentModel storeRendition(DocumentModel sourceDocument, Rendition rendition, String name) {
203        StoredRendition storedRendition = storeRendition(sourceDocument, rendition);
204        return storedRendition == null ? null : storedRendition.getHostDocument();
205    }
206
207    /**
208     * @since 8.1
209     */
210    protected StoredRendition storeRendition(DocumentModel sourceDocument, Rendition rendition) {
211        if (!rendition.isCompleted()) {
212            return null;
213        }
214        List<Blob> renderedBlobs = rendition.getBlobs();
215        Blob renderedBlob = renderedBlobs.get(0);
216        String mimeType = renderedBlob.getMimeType();
217        if (mimeType != null && mimeType.contains(LazyRendition.ERROR_MARKER)) {
218            return null;
219        }
220
221        CoreSession session = sourceDocument.getCoreSession();
222        DocumentModel version = null;
223        boolean isVersionable = sourceDocument.isVersionable();
224        if (isVersionable) {
225            DocumentRef versionRef = createVersionIfNeeded(sourceDocument, session);
226            version = session.getDocument(versionRef);
227        }
228
229        RenditionDefinition renditionDefinition = getRenditionDefinition(rendition.getName());
230        StoredRendition storedRendition = getStoredRenditionManager().createStoredRendition(sourceDocument, version,
231                renderedBlob, renditionDefinition);
232        return storedRendition;
233    }
234
235    protected DocumentRef createVersionIfNeeded(DocumentModel source, CoreSession session) {
236        if (source.isVersionable()) {
237            if (source.isVersion()) {
238                return source.getRef();
239            } else if (source.isCheckedOut()) {
240                DocumentRef versionRef = session.checkIn(source.getRef(), VersioningOption.MINOR, null);
241                source.refresh(DocumentModel.REFRESH_STATE, null);
242                return versionRef;
243            } else {
244                return session.getLastDocumentVersionRef(source.getRef());
245            }
246        }
247        return null;
248    }
249
250    /**
251     * @deprecated since 7.2. Not used.
252     */
253    @Deprecated
254    protected AutomationService getAutomationService() {
255        return Framework.getService(AutomationService.class);
256    }
257
258    @Override
259    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
260        if (RENDITION_DEFINITIONS_EP.equals(extensionPoint)) {
261            RenditionDefinition renditionDefinition = (RenditionDefinition) contribution;
262            renditionDefinitionRegistry.addContribution(renditionDefinition);
263        } else if (RENDITON_DEFINION_PROVIDERS_EP.equals(extensionPoint)) {
264            renditionDefinitionProviderRegistry.addContribution((RenditionDefinitionProviderDescriptor) contribution);
265        } else if (STORED_RENDITION_MANAGERS_EP.equals(extensionPoint)) {
266            storedRenditionManagerDescriptors.add(((StoredRenditionManagerDescriptor) contribution));
267        } else if (DEFAULT_RENDITION_EP.equals(extensionPoint)) {
268            defaultRenditionDescriptor = ((DefaultRenditionDescriptor) contribution);
269            // Save contribution
270            defaultRenditionDescriptors.add((DefaultRenditionDescriptor) contribution);
271        }
272    }
273
274    /**
275     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
276     */
277    @Deprecated
278    protected void registerRendition(RenditionDefinition renditionDefinition) {
279        String name = renditionDefinition.getName();
280        if (name == null) {
281            log.error("Cannot register rendition without a name");
282            return;
283        }
284        boolean enabled = renditionDefinition.isEnabled();
285        if (renditionDefinitions.containsKey(name)) {
286            log.info("Overriding rendition with name: " + name);
287            if (enabled) {
288                renditionDefinition = mergeRenditions(renditionDefinitions.get(name), renditionDefinition);
289            } else {
290                log.info("Disabled rendition with name " + name);
291                renditionDefinitions.remove(name);
292            }
293        }
294        if (enabled) {
295            log.info("Registering rendition with name: " + name);
296            renditionDefinitions.put(name, renditionDefinition);
297        }
298
299        // setup the Provider
300        setupProvider(renditionDefinition);
301    }
302
303    /**
304     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
305     */
306    @Deprecated
307    protected void setupProvider(RenditionDefinition definition) {
308        if (definition.getProviderClass() == null) {
309            definition.setProvider(new DefaultAutomationRenditionProvider());
310        } else {
311            try {
312                RenditionProvider provider = definition.getProviderClass().newInstance();
313                definition.setProvider(provider);
314            } catch (Exception e) {
315                log.error("Unable to create RenditionProvider", e);
316            }
317        }
318    }
319
320    protected RenditionDefinition mergeRenditions(RenditionDefinition oldRenditionDefinition,
321            RenditionDefinition newRenditionDefinition) {
322        String label = newRenditionDefinition.getLabel();
323        if (label != null) {
324            oldRenditionDefinition.label = label;
325        }
326
327        String operationChain = newRenditionDefinition.getOperationChain();
328        if (operationChain != null) {
329            oldRenditionDefinition.operationChain = operationChain;
330        }
331
332        return oldRenditionDefinition;
333    }
334
335    @Override
336    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
337        if (RENDITION_DEFINITIONS_EP.equals(extensionPoint)) {
338            renditionDefinitionRegistry.removeContribution((RenditionDefinition) contribution);
339        } else if (RENDITON_DEFINION_PROVIDERS_EP.equals(extensionPoint)) {
340            renditionDefinitionProviderRegistry.removeContribution(
341                    (RenditionDefinitionProviderDescriptor) contribution);
342        } else if (STORED_RENDITION_MANAGERS_EP.equals(extensionPoint)) {
343            storedRenditionManagerDescriptors.remove((contribution));
344        } else if (DEFAULT_RENDITION_EP.equals(extensionPoint)) {
345            defaultRenditionDescriptors.remove(contribution);
346            if (defaultRenditionDescriptors.size() == 0) {
347                // We don't have any default rendition descriptor
348                defaultRenditionDescriptor = null;
349            } else {
350                // Go back to the last contribution added
351                defaultRenditionDescriptor = defaultRenditionDescriptors.get(defaultRenditionDescriptors.size() - 1);
352            }
353        }
354    }
355
356    /**
357     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
358     */
359    @Deprecated
360    protected void unregisterRendition(RenditionDefinition renditionDefinition) {
361        String name = renditionDefinition.getName();
362        renditionDefinitions.remove(name);
363        log.info("Unregistering rendition with name: " + name);
364    }
365
366    @Override
367    public Rendition getRendition(DocumentModel doc, String renditionName) {
368        RenditionDefinition renditionDefinition = getAvailableRenditionDefinition(doc, renditionName);
369        return getRendition(doc, renditionDefinition, renditionDefinition.isStoreByDefault());
370    }
371
372    @Override
373    public Rendition getRendition(DocumentModel doc, String renditionName, boolean store) {
374        RenditionDefinition renditionDefinition = getAvailableRenditionDefinition(doc, renditionName);
375        return getRendition(doc, renditionDefinition, store);
376    }
377
378    protected Rendition getRendition(DocumentModel doc, RenditionDefinition renditionDefinition, boolean store) {
379
380        Rendition rendition = null;
381        boolean isVersionable = doc.isVersionable();
382        if (!isVersionable || !doc.isCheckedOut()) {
383            // stored renditions are only done against a non-versionable doc
384            // or a versionable doc that is not checkedout
385            rendition = getStoredRenditionManager().findStoredRendition(doc, renditionDefinition);
386            if (rendition != null) {
387                // Mark stored rendition as stale if needed
388                Calendar renditionModificationDate = (Calendar) rendition.getHostDocument().getPropertyValue(
389                        RENDITION_SOURCE_MODIFICATION_DATE_PROPERTY);
390                String sourceDocumentModificationDatePropertyName = renditionDefinition.getSourceDocumentModificationDatePropertyName();
391                Calendar currentModificationDate = (Calendar) doc.getPropertyValue(
392                        sourceDocumentModificationDatePropertyName);
393                if (!Objects.equals(renditionModificationDate, currentModificationDate)) {
394                    Blob blob = rendition.getBlob();
395                    if (blob != null) {
396                        blob.setMimeType(blob.getMimeType() + ";" + LazyRendition.STALE_MARKER);
397                    }
398                }
399                return rendition;
400            }
401        }
402
403        rendition = new LiveRendition(doc, renditionDefinition);
404
405        if (store) {
406            StoredRendition storedRendition = storeRendition(doc, rendition);
407            if (storedRendition != null) {
408                return storedRendition;
409            }
410        }
411        return rendition;
412    }
413
414    protected RenditionDefinition getAvailableRenditionDefinition(DocumentModel doc, String renditionName) {
415        RenditionDefinition renditionDefinition = renditionDefinitionRegistry.getRenditionDefinition(renditionName);
416        if (renditionDefinition == null) {
417            renditionDefinition = renditionDefinitionProviderRegistry.getRenditionDefinition(renditionName, doc);
418            if (renditionDefinition == null) {
419                String message = "The rendition definition '%s' is not registered";
420                throw new NuxeoException(String.format(message, renditionName));
421            }
422        } else {
423            // we have a rendition definition but we must check that it can be used for this doc
424            if (!renditionDefinitionRegistry.canUseRenditionDefinition(renditionDefinition, doc)) {
425                throw new NuxeoException("Rendition " + renditionName + " cannot be used for this doc " + doc.getId());
426            }
427        }
428        if (!renditionDefinition.getProvider().isAvailable(doc, renditionDefinition)) {
429            throw new NuxeoException(
430                    String.format("Rendition %s not available for this doc %s", renditionName, doc.getPathAsString()));
431        }
432        return renditionDefinition;
433    }
434
435    @Override
436    public List<Rendition> getAvailableRenditions(DocumentModel doc) {
437        return getAvailableRenditions(doc, false);
438    }
439
440    @Override
441    public List<Rendition> getAvailableRenditions(DocumentModel doc, boolean onlyVisible) {
442        List<Rendition> renditions = new ArrayList<>();
443
444        if (doc.isProxy()) {
445            return renditions;
446        }
447
448        List<RenditionDefinition> defs = getAvailableRenditionDefinitions(doc);
449        if (defs != null) {
450            for (RenditionDefinition def : defs) {
451                if (!onlyVisible || onlyVisible && def.isVisible()) {
452                    Rendition rendition = getRendition(doc, def.getName(), false);
453                    if (rendition != null) {
454                        renditions.add(rendition);
455                    }
456                }
457            }
458        }
459
460        return renditions;
461    }
462
463    @Override
464    public void deleteStoredRenditions(String repositoryName) {
465        StoredRenditionsCleaner cleaner = new StoredRenditionsCleaner(repositoryName);
466        cleaner.runUnrestricted();
467    }
468
469    private final class StoredRenditionsCleaner extends UnrestrictedSessionRunner {
470
471        private static final int BATCH_SIZE = 100;
472
473        private StoredRenditionsCleaner(String repositoryName) {
474            super(repositoryName);
475        }
476
477        @Override
478        public void run() {
479            Map<String, List<String>> sourceIdToRenditionRefs = computeLiveDocumentRefsToRenditionRefs();
480            removeStoredRenditions(sourceIdToRenditionRefs);
481        }
482
483        /**
484         * Computes only live documents renditions, the related versions will be deleted by Nuxeo.
485         */
486        private Map<String, List<String>> computeLiveDocumentRefsToRenditionRefs() {
487            Map<String, List<String>> liveDocumentRefsToRenditionRefs = new HashMap<>();
488            String query = String.format("SELECT %s, %s, %s FROM Document WHERE %s IS NOT NULL AND ecm:isVersion = 0",
489                    NXQL.ECM_UUID, RENDITION_SOURCE_ID_PROPERTY, RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY,
490                    RENDITION_SOURCE_ID_PROPERTY);
491            try (IterableQueryResult result = session.queryAndFetch(query, NXQL.NXQL)) {
492                for (Map<String, Serializable> res : result) {
493                    String renditionRef = res.get(NXQL.ECM_UUID).toString();
494                    String sourceId = res.get(RENDITION_SOURCE_ID_PROPERTY).toString();
495                    Serializable sourceVersionableId = res.get(RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY);
496
497                    String key = sourceVersionableId != null ? sourceVersionableId.toString() : sourceId;
498                    liveDocumentRefsToRenditionRefs.computeIfAbsent(key, k -> new ArrayList<>()).add(renditionRef);
499                }
500            }
501            return liveDocumentRefsToRenditionRefs;
502        }
503
504        private void removeStoredRenditions(Map<String, List<String>> liveDocumentRefsToRenditionRefs) {
505            List<String> liveDocumentRefs = new ArrayList<>(liveDocumentRefsToRenditionRefs.keySet());
506            if (liveDocumentRefs.isEmpty()) {
507                // no more document to check
508                return;
509            }
510
511            int processedSourceIds = 0;
512            while (processedSourceIds < liveDocumentRefs.size()) {
513                // compute the batch of source ids to check for existence
514                int limit = processedSourceIds + BATCH_SIZE > liveDocumentRefs.size() ? liveDocumentRefs.size()
515                        : processedSourceIds + BATCH_SIZE;
516                List<String> batchSourceIds = liveDocumentRefs.subList(processedSourceIds, limit);
517
518                // retrieve still existing documents
519                List<String> existingSourceIds = new ArrayList<>();
520                String query = NXQLQueryBuilder.getQuery("SELECT ecm:uuid FROM Document WHERE ecm:uuid IN ?",
521                        new Object[] { batchSourceIds }, true, true, null);
522                try (IterableQueryResult result = session.queryAndFetch(query, NXQL.NXQL)) {
523                    result.forEach(res -> existingSourceIds.add(res.get(NXQL.ECM_UUID).toString()));
524                }
525                batchSourceIds.removeAll(existingSourceIds);
526
527                List<String> renditionRefsToDelete = batchSourceIds.stream()
528                                                                   .map(liveDocumentRefsToRenditionRefs::get)
529                                                                   .reduce(new ArrayList<>(), (allRefs, refs) -> {
530                                                                       allRefs.addAll(refs);
531                                                                       return allRefs;
532                                                                   });
533
534                if (!renditionRefsToDelete.isEmpty()) {
535                    session.removeDocuments(
536                            renditionRefsToDelete.stream().map(IdRef::new).collect(Collectors.toList()).toArray(
537                                    new DocumentRef[renditionRefsToDelete.size()]));
538                }
539
540                if (TransactionHelper.isTransactionActiveOrMarkedRollback()) {
541                    TransactionHelper.commitOrRollbackTransaction();
542                    TransactionHelper.startTransaction();
543                }
544
545                // next batch
546                processedSourceIds += BATCH_SIZE;
547            }
548        }
549    }
550
551    @Override
552    public Rendition getDefaultRendition(DocumentModel doc, String reason, Map<String, Serializable> extendedInfos) {
553        if (defaultRenditionDescriptor == null) {
554            return null;
555        }
556        Map<String, Object> context = new HashMap<>();
557        Map<String, Serializable> ei = extendedInfos == null ? Collections.emptyMap() : extendedInfos;
558        NuxeoPrincipal currentUser = ClientLoginModule.getCurrentPrincipal();
559        context.put("Document", doc);
560        context.put("Reason", reason);
561        context.put("Infos", ei);
562        context.put("CurrentUser", currentUser);
563        ScriptEngine engine = scriptEngineManager.getEngineByName(defaultRenditionDescriptor.getScriptLanguage());
564        if (engine == null) {
565            throw new NuxeoException(
566                    "Engine not found for language: " + defaultRenditionDescriptor.getScriptLanguage());
567        }
568        if (!(engine instanceof Invocable)) {
569            throw new NuxeoException("Engine " + engine.getClass().getName() + " not Invocable for language: "
570                    + defaultRenditionDescriptor.getScriptLanguage());
571        }
572        Rendition rendition = null;
573        try {
574            engine.eval(defaultRenditionDescriptor.getScript());
575            engine.getBindings(ScriptContext.ENGINE_SCOPE).putAll(context);
576            Object result = ((Invocable) engine).invokeFunction("run");
577            if (!(result instanceof String)) {
578                log.error("Failed to get rendition name (" + result + ")");
579            } else {
580                String defaultRenditionName = (String) result;
581                if (StringUtils.isBlank(defaultRenditionName)) {
582                    log.info("Default rendition name cannot be evaluated, returning null");
583                } else {
584                    try {
585                        rendition = getRendition(doc, defaultRenditionName);
586                    } catch (NuxeoException e) {
587                        log.error("Unable to use default rendition " + defaultRenditionName, e);
588                    }
589                }
590            }
591
592        } catch (NoSuchMethodException e) {
593            throw new NuxeoException("Script does not contain function: run() in defaultRendition: ", e);
594        } catch (ScriptException e) {
595            log.error("Failed to evaluate script: ", e);
596        }
597        return rendition;
598    }
599
600}