001/*
002 * (C) Copyright 2006-20012 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 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 */
018package org.nuxeo.template.adapters.doc;
019
020import java.io.IOException;
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.List;
024
025import org.dom4j.DocumentException;
026import org.nuxeo.ecm.automation.AutomationService;
027import org.nuxeo.ecm.automation.OperationChain;
028import org.nuxeo.ecm.automation.OperationContext;
029import org.nuxeo.ecm.automation.OperationException;
030import org.nuxeo.ecm.automation.core.operations.blob.ConvertBlob;
031import org.nuxeo.ecm.core.api.Blob;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.DocumentRef;
034import org.nuxeo.ecm.core.api.IdRef;
035import org.nuxeo.ecm.core.api.NuxeoException;
036import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
037import org.nuxeo.runtime.api.Framework;
038import org.nuxeo.template.XMLSerializer;
039import org.nuxeo.template.adapters.AbstractTemplateDocument;
040import org.nuxeo.template.api.TemplateInput;
041import org.nuxeo.template.api.TemplateProcessor;
042import org.nuxeo.template.api.TemplateProcessorService;
043import org.nuxeo.template.api.adapters.TemplateBasedDocument;
044import org.nuxeo.template.api.adapters.TemplateSourceDocument;
045import org.nuxeo.template.api.descriptor.OutputFormatDescriptor;
046
047/**
048 * Default implementation of {@link TemplateBasedDocument} adapter. This adapter mainly expect from the underlying
049 * {@link DocumentModel} to have the "TemplateBased" facet
050 *
051 * @author Tiry (tdelprat@nuxeo.com)
052 */
053public class TemplateBasedDocumentAdapterImpl extends AbstractTemplateDocument implements Serializable,
054        TemplateBasedDocument {
055
056    private static final long serialVersionUID = 1L;
057
058    public static final String TEMPLATEBASED_FACET = "TemplateBased";
059
060    protected final TemplateBindings bindings;
061
062    public TemplateBasedDocumentAdapterImpl(DocumentModel doc) {
063        this.adaptedDoc = doc;
064        bindings = new TemplateBindings(doc);
065    }
066
067    public DocumentModel setTemplate(DocumentModel template, boolean save) {
068
069        TemplateSourceDocument source = template.getAdapter(TemplateSourceDocument.class);
070        if (source == null) {
071            throw new NuxeoException("Can not bind to an non template document");
072        }
073        String tid = source.getId();
074        String templateName = source.getName();
075        if (!bindings.containsTemplateId(tid)) {
076            if (templateName == null) {
077                templateName = TemplateBindings.DEFAULT_BINDING;
078            }
079            TemplateBinding tb = new TemplateBinding();
080            tb.setTemplateId(tid);
081            tb.setName(templateName);
082            bindings.add(tb);
083            initializeFromTemplate(templateName, false);
084            bindings.save(adaptedDoc);
085            if (save) {
086                doSave();
087            }
088
089        }
090        return adaptedDoc;
091    }
092
093    public DocumentModel removeTemplateBinding(String templateName, boolean save) {
094        if (bindings.containsTemplateName(templateName)) {
095            bindings.removeByName(templateName);
096            bindings.save(adaptedDoc);
097            if (save) {
098                doSave();
099            }
100        }
101        return adaptedDoc;
102    }
103
104    public TemplateSourceDocument getSourceTemplate(String templateName) {
105        DocumentModel template = getSourceTemplateDoc(templateName);
106        if (template != null) {
107            return template.getAdapter(TemplateSourceDocument.class);
108        }
109        return null;
110    }
111
112    @Override
113    public DocumentRef getSourceTemplateDocRef(String templateName) {
114        TemplateBinding binding = null;
115        if (templateName == null) {
116            binding = bindings.get();
117        } else {
118            binding = bindings.get(templateName);
119        }
120        if (binding == null) {
121            return null;
122        }
123        return new IdRef(binding.getTemplateId());
124    }
125
126    public DocumentModel getSourceTemplateDoc(String templateName) {
127        TemplateBinding binding = null;
128        if (templateName == null) {
129            binding = bindings.get();
130        } else {
131            binding = bindings.get(templateName);
132        }
133        if (binding == null) {
134            return null;
135        }
136        DocumentRef tRef = getSourceTemplateDocRef(templateName);
137        if (tRef == null) {
138            return null;
139        }
140        return getSession().getDocument(tRef);
141    }
142
143    public List<TemplateSourceDocument> getSourceTemplates() {
144        List<TemplateSourceDocument> result = new ArrayList<TemplateSourceDocument>();
145        for (TemplateBinding binding : bindings) {
146            result.add(getSourceTemplate(binding.getName()));
147        }
148        return result;
149    }
150
151    public String getTemplateType(String templateName) {
152        TemplateSourceDocument source = getSourceTemplate(templateName);
153        if (source != null) {
154            return source.getTemplateType();
155        }
156        return null;
157    }
158
159    public DocumentModel initializeFromTemplate(boolean save) {
160        return initializeFromTemplate(TemplateBindings.DEFAULT_BINDING, save);
161    }
162
163    public DocumentModel initializeFromTemplate(String templateName, boolean save) {
164
165        TemplateSourceDocument tmpl = getSourceTemplate(templateName);
166        if (tmpl == null) {
167            throw new NuxeoException("No associated template for name " + templateName);
168        }
169
170        // copy Params but set as readonly all params set in template
171        List<TemplateInput> params = tmpl.getParams();
172        List<TemplateInput> myParams = new ArrayList<TemplateInput>();
173        for (TemplateInput param : params) {
174            boolean readOnly = param.isSet() && !tmpl.allowInstanceOverride();
175            TemplateInput myParam = param.getCopy(readOnly);
176            myParams.add(myParam);
177        }
178
179        bindings.get(templateName).setData(myParams);
180
181        if (tmpl.useAsMainContent()) {
182            // copy the template as main blob
183            BlobHolder bh = adaptedDoc.getAdapter(BlobHolder.class);
184            if (bh != null) {
185                bh.setBlob(tmpl.getTemplateBlob());
186            }
187            bindings.get(templateName).setUseMainContentAsTemplate(true);
188        }
189
190        if (save) {
191            doSave();
192        }
193        return adaptedDoc;
194    }
195
196    @Override
197    protected void doSave() {
198        bindings.save(adaptedDoc);
199        super.doSave();
200    }
201
202    protected void setBlob(Blob blob) {
203        adaptedDoc.getAdapter(BlobHolder.class).setBlob(blob);
204    }
205
206    public Blob renderWithTemplate(String templateName) {
207        TemplateProcessor processor = getTemplateProcessor(templateName);
208        if (processor != null) {
209            Blob blob;
210            try {
211                blob = processor.renderTemplate(this, templateName);
212            } catch (IOException e) {
213                throw new NuxeoException("Failed to render template: " + templateName, e);
214            }
215            String format = getSourceTemplate(templateName).getOutputFormat();
216            if (blob != null && format != null && !format.isEmpty()) {
217                try {
218                    return convertBlob(templateName, blob, format);
219                } catch (OperationException e) {
220                    throw new NuxeoException(e);
221                }
222            } else {
223                return blob;
224            }
225        } else {
226            String templateType = getTemplateType(templateName);
227            if (templateType == null) {
228                throw new NuxeoException(
229                        "Template type is null : if you don't set it explicitly, your template file should have an extension or a mimetype so that it can be automatically determined");
230            } else {
231                throw new NuxeoException("No template processor found for template type=" + templateType);
232            }
233        }
234    }
235
236    private Blob convertBlob(String templateName, Blob blob, String outputFormat) throws OperationException {
237        OutputFormatDescriptor outFormat = getOutputFormatDescriptor(outputFormat);
238        String chainId = outFormat.getChainId();
239        String mimeType = outFormat.getMimeType();
240        AutomationService automationService = Framework.getLocalService(AutomationService.class);
241        OperationContext ctx = initOperationContext(blob, templateName);
242        Object result = null;
243        if (chainId != null) {
244            ctx.put("templateSourceDocument", getSourceTemplateDoc(templateName));
245            ctx.put("templateBasedDocument", adaptedDoc);
246            result = automationService.run(ctx, chainId);
247        } else if (mimeType != null) {
248            OperationChain chain = new OperationChain("convertToMimeType");
249            chain.add(ConvertBlob.ID).set("mimeType", mimeType);
250            result = automationService.run(ctx, chain);
251        }
252        if (result != null && result instanceof Blob) {
253            return (Blob) result;
254        } else {
255            return blob;
256        }
257    }
258
259    protected OperationContext initOperationContext(Blob blob, String templateName) {
260        OperationContext ctx = new OperationContext();
261        ctx.put("templateName", templateName);
262        ctx.setInput(blob);
263        ctx.setCommit(false);
264        ctx.setCoreSession(getSession());
265        return ctx;
266    }
267
268    public Blob renderAndStoreAsAttachment(String templateName, boolean save) {
269        Blob blob = renderWithTemplate(templateName);
270        setBlob(blob);
271        if (save) {
272            adaptedDoc = getSession().saveDocument(adaptedDoc);
273        }
274        return blob;
275    }
276
277    public boolean isBidirectional() {
278        /*
279         * TemplateProcessor processor = getTemplateProcessor(); if (processor != null) { return processor instanceof
280         * BidirectionalTemplateProcessor; }
281         */
282        return false;
283    }
284
285    public Blob getTemplateBlob(String templateName) {
286        TemplateSourceDocument source = getSourceTemplate(templateName);
287        if (source != null) {
288            if (source.useAsMainContent()) {
289                BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class);
290                if (bh != null) {
291                    Blob blob = bh.getBlob();
292                    if (blob != null) {
293                        return blob;
294                    }
295                }
296            }
297            // get the template from the source
298            Blob blob = source.getTemplateBlob();
299            return blob;
300        }
301        // fall back
302        BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class);
303        if (bh == null) {
304            return null;
305        } else {
306            return bh.getBlob();
307        }
308    }
309
310    public boolean hasParams(String templateName) {
311        return getParams(templateName).size() > 0;
312    }
313
314    public List<TemplateInput> getParams(String templateName) {
315
316        TemplateBinding binding = bindings.get(templateName);
317        if (binding != null) {
318            String xml = binding.getData();
319            try {
320                return XMLSerializer.readFromXml(xml);
321            } catch (DocumentException e) {
322                log.error("Unable to parse parameters", e);
323                return new ArrayList<TemplateInput>();
324            }
325        }
326        return new ArrayList<TemplateInput>();
327    }
328
329    public DocumentModel saveParams(String templateName, List<TemplateInput> params, boolean save) {
330        TemplateBinding binding = bindings.get(templateName);
331        if (binding != null) {
332            binding.setData(params);
333            bindings.save(adaptedDoc);
334        }
335        if (save) {
336            doSave();
337        }
338        return adaptedDoc;
339    }
340
341    protected TemplateProcessor getTemplateProcessor(String templateName) {
342        TemplateProcessorService tps = Framework.getLocalService(TemplateProcessorService.class);
343        return tps.getProcessor(getTemplateType(templateName));
344    }
345
346    protected OutputFormatDescriptor getOutputFormatDescriptor(String outputFormat) {
347        TemplateProcessorService tps = Framework.getLocalService(TemplateProcessorService.class);
348        return tps.getOutputFormatDescriptor(outputFormat);
349    }
350
351    public boolean hasEditableParams(String templateName) {
352        for (TemplateInput param : getParams(templateName)) {
353            if (!param.isReadOnly()) {
354                return true;
355            }
356        }
357        return false;
358    }
359
360    public String getTemplateNameForRendition(String renditionName) {
361        for (TemplateBinding binding : bindings) {
362            if (renditionName.equals(getSourceTemplate(binding.getName()).getTargetRenditionName())) {
363                return binding.getName();
364            }
365        }
366        return null;
367    }
368
369    public List<String> getTemplateNames() {
370        return bindings.getNames();
371    }
372
373}