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