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