001/*
002 * (C) Copyright 2010 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 * Contributors:
016 * Nuxeo - initial API and implementation
017 */
018
019package org.nuxeo.ecm.platform.rendition.service;
020
021import java.util.ArrayList;
022import java.util.Deque;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.ecm.automation.AutomationService;
031import org.nuxeo.ecm.core.api.Blob;
032import org.nuxeo.ecm.core.api.CoreSession;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.DocumentRef;
035import org.nuxeo.ecm.core.api.NuxeoException;
036import org.nuxeo.ecm.core.api.VersioningOption;
037import org.nuxeo.ecm.platform.rendition.Rendition;
038import org.nuxeo.ecm.platform.rendition.extension.DefaultAutomationRenditionProvider;
039import org.nuxeo.ecm.platform.rendition.extension.RenditionProvider;
040import org.nuxeo.ecm.platform.rendition.impl.LiveRendition;
041import org.nuxeo.ecm.platform.rendition.impl.StoredRendition;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.model.ComponentContext;
044import org.nuxeo.runtime.model.ComponentInstance;
045import org.nuxeo.runtime.model.DefaultComponent;
046
047/**
048 * Default implementation of {@link RenditionService}.
049 *
050 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
051 * @since 5.4.1
052 */
053public class RenditionServiceImpl extends DefaultComponent implements RenditionService {
054
055    public static final String RENDITION_DEFINITIONS_EP = "renditionDefinitions";
056
057    public static final String RENDITON_DEFINION_PROVIDERS_EP = "renditionDefinitionProviders";
058
059    /**
060     * @since 8.1
061     */
062    public static final String STORED_RENDITION_MANAGERS_EP = "storedRenditionManagers";
063
064    private static final Log log = LogFactory.getLog(RenditionServiceImpl.class);
065
066    /**
067     * @deprecated since 7.2. Not used.
068     */
069    @Deprecated
070    protected AutomationService automationService;
071
072    /**
073     * @deprecated since 7.3.
074     */
075    @Deprecated
076    protected Map<String, RenditionDefinition> renditionDefinitions;
077
078    /**
079     * @since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
080     */
081    protected RenditionDefinitionRegistry renditionDefinitionRegistry;
082
083    protected RenditionDefinitionProviderRegistry renditionDefinitionProviderRegistry;
084
085    protected static final StoredRenditionManager DEFAULT_STORED_RENDITION_MANAGER = new DefaultStoredRenditionManager();
086
087    /**
088     * @since 8.1
089     */
090    protected Deque<StoredRenditionManagerDescriptor> storedRenditionManagerDescriptors = new LinkedList<>();
091
092    /**
093     * @since 8.1
094     */
095    public StoredRenditionManager getStoredRenditionManager() {
096        StoredRenditionManagerDescriptor descr = storedRenditionManagerDescriptors.peekLast();
097        return descr == null ? DEFAULT_STORED_RENDITION_MANAGER : descr.getStoredRenditionManager();
098    }
099
100    @Override
101    public void activate(ComponentContext context) {
102        renditionDefinitions = new HashMap<>();
103        renditionDefinitionRegistry = new RenditionDefinitionRegistry();
104        renditionDefinitionProviderRegistry = new RenditionDefinitionProviderRegistry();
105        super.activate(context);
106    }
107
108    @Override
109    public void deactivate(ComponentContext context) {
110        renditionDefinitions = null;
111        renditionDefinitionRegistry = null;
112        renditionDefinitionProviderRegistry = null;
113        super.deactivate(context);
114    }
115
116    public RenditionDefinition getRenditionDefinition(String name) {
117        return renditionDefinitionRegistry.getRenditionDefinition(name);
118    }
119
120    @Override
121    @Deprecated
122    public List<RenditionDefinition> getDeclaredRenditionDefinitions() {
123        return new ArrayList<>(renditionDefinitionRegistry.descriptors.values());
124    }
125
126    @Override
127    @Deprecated
128    public List<RenditionDefinition> getDeclaredRenditionDefinitionsForProviderType(String providerType) {
129        List<RenditionDefinition> defs = new ArrayList<>();
130        for (RenditionDefinition def : getDeclaredRenditionDefinitions()) {
131            if (def.getProviderType().equals(providerType)) {
132                defs.add(def);
133            }
134        }
135        return defs;
136    }
137
138    @Override
139    public List<RenditionDefinition> getAvailableRenditionDefinitions(DocumentModel doc) {
140
141        List<RenditionDefinition> defs = new ArrayList<>();
142        defs.addAll(renditionDefinitionRegistry.getRenditionDefinitions(doc));
143        defs.addAll(renditionDefinitionProviderRegistry.getRenditionDefinitions(doc));
144
145        // XXX what about "lost renditions" ?
146        return defs;
147    }
148
149    @Override
150    public DocumentRef storeRendition(DocumentModel source, String renditionDefinitionName) {
151        Rendition rendition = getRendition(source, renditionDefinitionName, true);
152        return rendition == null ? null : rendition.getHostDocument().getRef();
153    }
154
155    /**
156     * @deprecated since 8.1
157     */
158    @Deprecated
159    protected DocumentModel storeRendition(DocumentModel sourceDocument, Rendition rendition, String name) {
160        StoredRendition storedRendition = storeRendition(sourceDocument, rendition);
161        return storedRendition == null ? null : storedRendition.getHostDocument();
162    }
163
164    /**
165     * @since 8.1
166     */
167    protected StoredRendition storeRendition(DocumentModel sourceDocument, Rendition rendition) {
168        if (!rendition.isCompleted()) {
169            return null;
170        }
171        List<Blob> renderedBlobs = rendition.getBlobs();
172        Blob renderedBlob = renderedBlobs.get(0);
173        CoreSession session = sourceDocument.getCoreSession();
174        DocumentModel version = null;
175        boolean isVersionable = sourceDocument.isVersionable();
176        if (isVersionable) {
177            DocumentRef versionRef = createVersionIfNeeded(sourceDocument, session);
178            version = session.getDocument(versionRef);
179        }
180
181        RenditionDefinition renditionDefinition = getRenditionDefinition(rendition.getName());
182        StoredRendition storedRendition = getStoredRenditionManager().createStoredRendition(sourceDocument, version,
183                renderedBlob, renditionDefinition);
184        return storedRendition;
185    }
186
187    protected DocumentRef createVersionIfNeeded(DocumentModel source, CoreSession session) {
188        if (source.isVersionable()) {
189            if (source.isVersion()) {
190                return source.getRef();
191            } else if (source.isCheckedOut()) {
192                DocumentRef versionRef = session.checkIn(source.getRef(), VersioningOption.MINOR, null);
193                source.refresh(DocumentModel.REFRESH_STATE, null);
194                return versionRef;
195            } else {
196                return session.getLastDocumentVersionRef(source.getRef());
197            }
198        }
199        return null;
200    }
201
202    /**
203     * @deprecated since 7.2. Not used.
204     */
205    @Deprecated
206    protected AutomationService getAutomationService() {
207        return Framework.getService(AutomationService.class);
208    }
209
210    @Override
211    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
212        if (RENDITION_DEFINITIONS_EP.equals(extensionPoint)) {
213            RenditionDefinition renditionDefinition = (RenditionDefinition) contribution;
214            renditionDefinitionRegistry.addContribution(renditionDefinition);
215        } else if (RENDITON_DEFINION_PROVIDERS_EP.equals(extensionPoint)) {
216            renditionDefinitionProviderRegistry.addContribution((RenditionDefinitionProviderDescriptor) contribution);
217        } else if (STORED_RENDITION_MANAGERS_EP.equals(extensionPoint)) {
218            storedRenditionManagerDescriptors.add(((StoredRenditionManagerDescriptor) contribution));
219        }
220    }
221
222    /**
223     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
224     */
225    @Deprecated
226    protected void registerRendition(RenditionDefinition renditionDefinition) {
227        String name = renditionDefinition.getName();
228        if (name == null) {
229            log.error("Cannot register rendition without a name");
230            return;
231        }
232        boolean enabled = renditionDefinition.isEnabled();
233        if (renditionDefinitions.containsKey(name)) {
234            log.info("Overriding rendition with name: " + name);
235            if (enabled) {
236                renditionDefinition = mergeRenditions(renditionDefinitions.get(name), renditionDefinition);
237            } else {
238                log.info("Disabled rendition with name " + name);
239                renditionDefinitions.remove(name);
240            }
241        }
242        if (enabled) {
243            log.info("Registering rendition with name: " + name);
244            renditionDefinitions.put(name, renditionDefinition);
245        }
246
247        // setup the Provider
248        setupProvider(renditionDefinition);
249    }
250
251    /**
252     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
253     */
254    @Deprecated
255    protected void setupProvider(RenditionDefinition definition) {
256        if (definition.getProviderClass() == null) {
257            definition.setProvider(new DefaultAutomationRenditionProvider());
258        } else {
259            try {
260                RenditionProvider provider = definition.getProviderClass().newInstance();
261                definition.setProvider(provider);
262            } catch (Exception e) {
263                log.error("Unable to create RenditionProvider", e);
264            }
265        }
266    }
267
268    protected RenditionDefinition mergeRenditions(RenditionDefinition oldRenditionDefinition,
269            RenditionDefinition newRenditionDefinition) {
270        String label = newRenditionDefinition.getLabel();
271        if (label != null) {
272            oldRenditionDefinition.label = label;
273        }
274
275        String operationChain = newRenditionDefinition.getOperationChain();
276        if (operationChain != null) {
277            oldRenditionDefinition.operationChain = operationChain;
278        }
279
280        return oldRenditionDefinition;
281    }
282
283    @Override
284    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
285        if (RENDITION_DEFINITIONS_EP.equals(extensionPoint)) {
286            renditionDefinitionRegistry.removeContribution((RenditionDefinition) contribution);
287        } else if (RENDITON_DEFINION_PROVIDERS_EP.equals(extensionPoint)) {
288            renditionDefinitionProviderRegistry.removeContribution((RenditionDefinitionProviderDescriptor) contribution);
289        } else if (STORED_RENDITION_MANAGERS_EP.equals(extensionPoint)) {
290            storedRenditionManagerDescriptors.remove(((StoredRenditionManagerDescriptor) contribution));
291        }
292    }
293
294    /**
295     * @deprecated since 7.3. RenditionDefinitions are store in {@link #renditionDefinitionRegistry}.
296     */
297    @Deprecated
298    protected void unregisterRendition(RenditionDefinition renditionDefinition) {
299        String name = renditionDefinition.getName();
300        renditionDefinitions.remove(name);
301        log.info("Unregistering rendition with name: " + name);
302    }
303
304    @Override
305    public Rendition getRendition(DocumentModel doc, String renditionName) {
306        RenditionDefinition renditionDefinition = getAvailableRenditionDefinition(doc, renditionName);
307        return getRendition(doc, renditionDefinition, renditionDefinition.isStoreByDefault());
308    }
309
310    @Override
311    public Rendition getRendition(DocumentModel doc, String renditionName, boolean store) {
312        RenditionDefinition renditionDefinition = getAvailableRenditionDefinition(doc, renditionName);
313        return getRendition(doc, renditionDefinition, store);
314    }
315
316    protected Rendition getRendition(DocumentModel doc, RenditionDefinition renditionDefinition, boolean store) {
317
318        Rendition rendition = null;
319        boolean isVersionable = doc.isVersionable();
320        if (!isVersionable || !doc.isCheckedOut()) {
321            // stored renditions are only done against a non-versionable doc
322            // or a versionable doc that is not checkedout
323            rendition = getStoredRenditionManager().findStoredRendition(doc, renditionDefinition);
324            if (rendition != null) {
325                return rendition;
326            }
327        }
328
329        rendition = new LiveRendition(doc, renditionDefinition);
330
331        if (store) {
332            StoredRendition storedRendition = storeRendition(doc, rendition);
333            if (storedRendition != null) {
334                return storedRendition;
335            }
336        }
337        return rendition;
338    }
339
340    protected RenditionDefinition getAvailableRenditionDefinition(DocumentModel doc, String renditionName) {
341        RenditionDefinition renditionDefinition = renditionDefinitionRegistry.getRenditionDefinition(renditionName);
342        if (renditionDefinition == null) {
343            renditionDefinition = renditionDefinitionProviderRegistry.getRenditionDefinition(renditionName, doc);
344            if (renditionDefinition == null) {
345                String message = "The rendition definition '%s' is not registered";
346                throw new NuxeoException(String.format(message, renditionName));
347            }
348        } else {
349            // we have a rendition definition but we must check that it can be used for this doc
350            if (!renditionDefinitionRegistry.canUseRenditionDefinition(renditionDefinition, doc)) {
351                throw new NuxeoException("Rendition " + renditionName + " cannot be used for this doc " + doc.getId());
352            }
353        }
354        if (!renditionDefinition.getProvider().isAvailable(doc, renditionDefinition)) {
355            throw new NuxeoException("Rendition " + renditionName + " not available for this doc " + doc.getId());
356        }
357        return renditionDefinition;
358    }
359
360    @Override
361    public List<Rendition> getAvailableRenditions(DocumentModel doc) {
362        return getAvailableRenditions(doc, false);
363    }
364
365    @Override
366    public List<Rendition> getAvailableRenditions(DocumentModel doc, boolean onlyVisible) {
367        List<Rendition> renditions = new ArrayList<>();
368
369        if (doc.isProxy()) {
370            return renditions;
371        }
372
373        List<RenditionDefinition> defs = getAvailableRenditionDefinitions(doc);
374        if (defs != null) {
375            for (RenditionDefinition def : defs) {
376                if (!onlyVisible || onlyVisible && def.isVisible()) {
377                    Rendition rendition = getRendition(doc, def.getName(), false);
378                    if (rendition != null) {
379                        renditions.add(rendition);
380                    }
381                }
382            }
383        }
384
385        return renditions;
386    }
387}