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