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