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.getLocalService(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}