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