001/*
002 * (C) Copyright 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 *     Thierry Delprat
018 */
019package org.nuxeo.template.processors.fm;
020
021import java.io.IOException;
022import java.io.StringWriter;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026import java.util.regex.Pattern;
027
028import org.nuxeo.common.utils.FileUtils;
029import org.nuxeo.ecm.core.api.Blob;
030import org.nuxeo.ecm.core.api.Blobs;
031import org.nuxeo.ecm.core.api.NuxeoException;
032import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
033import org.nuxeo.ecm.platform.rendering.api.RenderingException;
034import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine;
035import org.nuxeo.runtime.api.Framework;
036import org.nuxeo.template.api.TemplateInput;
037import org.nuxeo.template.api.TemplateProcessor;
038import org.nuxeo.template.api.adapters.TemplateBasedDocument;
039import org.nuxeo.template.fm.FMContextBuilder;
040import org.nuxeo.template.fm.FreeMarkerVariableExtractor;
041import org.nuxeo.template.processors.AbstractTemplateProcessor;
042
043import freemarker.cache.StringTemplateLoader;
044
045public class FreeMarkerProcessor extends AbstractTemplateProcessor implements TemplateProcessor {
046
047    protected StringTemplateLoader loader = new StringTemplateLoader();
048
049    protected FreemarkerEngine fmEngine = null;
050
051    protected FMContextBuilder fmContextBuilder = new FMContextBuilder();
052
053    protected FreemarkerEngine getEngine() {
054        if (fmEngine == null) {
055            fmEngine = new FreemarkerEngine();
056            fmEngine.getConfiguration().setTemplateLoader(loader);
057        }
058        return fmEngine;
059    }
060
061    protected final static Pattern XMLStartPattern = Pattern.compile("<\\?xml");
062
063    protected final static Pattern HtmlTagPattern = Pattern.compile("<(\\S+?)(.*?)>(.*?)</\\1>",
064            Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
065
066    protected String guessMimeType(Blob result, MimetypeRegistry mreg) {
067
068        if (result == null) {
069            return null;
070        }
071
072        String content;
073        try {
074            content = result.getString();
075        } catch (IOException e) {
076            throw new NuxeoException(e);
077        }
078
079        if (XMLStartPattern.matcher(content).find()) {
080            return "text/xml";
081        }
082
083        if (HtmlTagPattern.matcher(content).find()) {
084            return "text/html";
085        }
086
087        return mreg.getMimetypeFromBlobWithDefault(result, "text/plain");
088    }
089
090    protected void setBlobAttributes(Blob result, TemplateBasedDocument templateBasedDocument) {
091
092        // try to guess mimetype and extension of the resulting Blob
093
094        MimetypeRegistry mreg = Framework.getService(MimetypeRegistry.class);
095
096        String mimetype = "text/html";
097        String extension = ".html";
098
099        if (mreg != null) {
100            String found_mimetype = guessMimeType(result, mreg);
101            if (found_mimetype != null) {
102                mimetype = found_mimetype;
103                List<String> extensions = mreg.getExtensionsFromMimetypeName(mimetype);
104                if (extensions != null && extensions.size() > 0) {
105                    extension = "." + extensions.get(0);
106                }
107            }
108        }
109        if ("text/xml".equalsIgnoreCase(mimetype)) {
110            // because MimetypeRegistry return a stupid result for XML
111            extension = ".xml";
112        }
113        result.setMimeType(mimetype);
114        String targetFileName = FileUtils.getFileNameNoExt(templateBasedDocument.getAdaptedDoc().getTitle());
115        result.setFilename(targetFileName + extension);
116    }
117
118    @Override
119    public Blob renderTemplate(TemplateBasedDocument templateBasedDocument, String templateName) throws IOException {
120
121        Blob sourceTemplateBlob = getSourceTemplateBlob(templateBasedDocument, templateName);
122
123        String fmTemplateKey = "main" + System.currentTimeMillis();
124
125        String ftl = sourceTemplateBlob.getString();
126
127        loader.putTemplate(fmTemplateKey, ftl);
128
129        Map<String, Object> ctx = fmContextBuilder.build(templateBasedDocument, templateName);
130
131        FMBindingResolver resolver = new FMBindingResolver();
132        resolver.resolve(templateBasedDocument.getParams(templateName), ctx, templateBasedDocument);
133
134        StringWriter writer = new StringWriter();
135        try {
136            getEngine().render(fmTemplateKey, ctx, writer);
137        } catch (RenderingException e) {
138            throw new IOException(e);
139        }
140
141        Blob result = Blobs.createBlob(writer.toString());
142        setBlobAttributes(result, templateBasedDocument);
143
144        return result;
145    }
146
147    @Override
148    public List<TemplateInput> getInitialParametersDefinition(Blob blob) throws IOException {
149        List<TemplateInput> params = new ArrayList<TemplateInput>();
150
151        if (blob != null) {
152            String xmlContent = blob.getString();
153
154            if (xmlContent != null) {
155                List<String> vars = FreeMarkerVariableExtractor.extractVariables(xmlContent);
156
157                for (String var : vars) {
158                    TemplateInput input = new TemplateInput(var);
159                    params.add(input);
160                }
161            }
162        }
163        return params;
164    }
165
166}