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