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.nuxeo.ecm.automation.AutomationService;
031import org.nuxeo.ecm.automation.OperationChain;
032import org.nuxeo.ecm.automation.OperationContext;
033import org.nuxeo.ecm.automation.OperationException;
034import org.nuxeo.ecm.automation.core.operations.blob.ConvertBlob;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.DocumentNotFoundException;
038import org.nuxeo.ecm.core.api.DocumentRef;
039import org.nuxeo.ecm.core.api.DocumentSecurityException;
040import org.nuxeo.ecm.core.api.IdRef;
041import org.nuxeo.ecm.core.api.NuxeoException;
042import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
043import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
044import org.nuxeo.runtime.api.Framework;
045import org.nuxeo.template.adapters.AbstractTemplateDocument;
046import org.nuxeo.template.api.TemplateInput;
047import org.nuxeo.template.api.TemplateProcessor;
048import org.nuxeo.template.api.TemplateProcessorService;
049import org.nuxeo.template.api.adapters.TemplateBasedDocument;
050import org.nuxeo.template.api.adapters.TemplateSourceDocument;
051import org.nuxeo.template.api.descriptor.OutputFormatDescriptor;
052import org.nuxeo.template.serializer.service.TemplateSerializerService;
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
061        implements Serializable, 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<>();
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<>();
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                if (blob.getMimeType() == null) {
241                    MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
242                    String mimeType = mimetypeRegistry.getMimetypeFromFilenameAndBlobWithDefault(blob.getFilename(),
243                            blob, null);
244                    blob.setMimeType(mimeType);
245                }
246            } catch (IOException e) {
247                throw new NuxeoException("Failed to render template: " + templateName, e);
248            }
249            TemplateSourceDocument template = getSourceTemplate(templateName);
250            if (template == null) {
251                throw new NuxeoException("No associated template for name " + templateName);
252            }
253            String format = template.getOutputFormat();
254            if (blob != null && format != null && !format.isEmpty()) {
255                try {
256                    return convertBlob(templateName, blob, format);
257                } catch (OperationException e) {
258                    throw new NuxeoException(e);
259                }
260            } else {
261                return blob;
262            }
263        } else {
264            String templateType = getTemplateType(templateName);
265            if (templateType == null) {
266                throw new NuxeoException(
267                        "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");
268            } else {
269                throw new NuxeoException("No template processor found for template type=" + templateType);
270            }
271        }
272    }
273
274    private Blob convertBlob(String templateName, Blob blob, String outputFormat) throws OperationException {
275        OutputFormatDescriptor outFormat = getOutputFormatDescriptor(outputFormat);
276        String chainId = outFormat.getChainId();
277        String mimeType = outFormat.getMimeType();
278        AutomationService automationService = Framework.getService(AutomationService.class);
279        try (OperationContext ctx = initOperationContext(blob, templateName)) {
280            Object result = null;
281            if (chainId != null) {
282                ctx.put("templateSourceDocument", getSourceTemplateDoc(templateName));
283                ctx.put("templateBasedDocument", adaptedDoc);
284                result = automationService.run(ctx, chainId);
285            } else if (mimeType != null) {
286                OperationChain chain = new OperationChain("convertToMimeType");
287                chain.add(ConvertBlob.ID).set("mimeType", mimeType);
288                result = automationService.run(ctx, chain);
289            }
290            if (result != null && result instanceof Blob) {
291                return (Blob) result;
292            } else {
293                return blob;
294            }
295        }
296    }
297
298    protected OperationContext initOperationContext(Blob blob, String templateName) {
299        OperationContext ctx = new OperationContext();
300        ctx.put("templateName", templateName);
301        ctx.setInput(blob);
302        ctx.setCommit(false);
303        ctx.setCoreSession(getSession());
304        return ctx;
305    }
306
307    @Override
308    public Blob renderAndStoreAsAttachment(String templateName, boolean save) {
309        Blob blob = renderWithTemplate(templateName);
310        setBlob(blob);
311        if (save) {
312            adaptedDoc = getSession().saveDocument(adaptedDoc);
313        }
314        return blob;
315    }
316
317    public boolean isBidirectional() {
318        /*
319         * TemplateProcessor processor = getTemplateProcessor(); if (processor != null) { return processor instanceof
320         * BidirectionalTemplateProcessor; }
321         */
322        return false;
323    }
324
325    @Override
326    public Blob getTemplateBlob(String templateName) {
327        TemplateSourceDocument source = getSourceTemplate(templateName);
328        if (source != null) {
329            if (source.useAsMainContent()) {
330                BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class);
331                if (bh != null) {
332                    Blob blob = bh.getBlob();
333                    if (blob != null) {
334                        return blob;
335                    }
336                }
337            }
338            // get the template from the source
339            Blob blob = source.getTemplateBlob();
340            return blob;
341        }
342        // fall back
343        BlobHolder bh = getAdaptedDoc().getAdapter(BlobHolder.class);
344        if (bh == null) {
345            return null;
346        } else {
347            return bh.getBlob();
348        }
349    }
350
351    @Override
352    public boolean hasParams(String templateName) {
353        return getParams(templateName).size() > 0;
354    }
355
356    @Override
357    public List<TemplateInput> getParams(String templateName) {
358
359        TemplateBinding binding = bindings.get(templateName);
360        if (binding != null) {
361            String xml = binding.getData();
362            try {
363                return Framework.getService(TemplateSerializerService.class).deserializeXML(xml);
364            } catch (NuxeoException e) {
365                log.error("Unable to parse parameters", e);
366                return new ArrayList<>();
367            }
368        }
369        return new ArrayList<>();
370    }
371
372    @Override
373    public DocumentModel saveParams(String templateName, List<TemplateInput> params, boolean save) {
374        TemplateBinding binding = bindings.get(templateName);
375        if (binding != null) {
376            binding.setData(params);
377            bindings.save(adaptedDoc);
378        }
379        if (save) {
380            doSave();
381        }
382        return adaptedDoc;
383    }
384
385    protected TemplateProcessor getTemplateProcessor(String templateName) {
386        TemplateProcessorService tps = Framework.getService(TemplateProcessorService.class);
387        return tps.getProcessor(getTemplateType(templateName));
388    }
389
390    protected OutputFormatDescriptor getOutputFormatDescriptor(String outputFormat) {
391        TemplateProcessorService tps = Framework.getService(TemplateProcessorService.class);
392        return tps.getOutputFormatDescriptor(outputFormat);
393    }
394
395    @Override
396    public boolean hasEditableParams(String templateName) {
397        for (TemplateInput param : getParams(templateName)) {
398            if (!param.isReadOnly()) {
399                return true;
400            }
401        }
402        return false;
403    }
404
405    @Override
406    public String getTemplateNameForRendition(String renditionName) {
407        for (TemplateBinding binding : bindings) {
408            TemplateSourceDocument template = getSourceTemplate(binding.getName());
409            if (template != null && renditionName.equals(template.getTargetRenditionName())) {
410                return binding.getName();
411            }
412        }
413        return null;
414    }
415
416    @Override
417    public List<String> getTemplateNames() {
418        return bindings.getNames();
419    }
420
421}