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