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; 020 021import static org.nuxeo.template.api.ContentInputType.BlobContent; 022import static org.nuxeo.template.api.ContentInputType.HtmlPreview; 023import static org.nuxeo.template.api.InputType.Content; 024 025import java.io.IOException; 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.stream.Collectors; 034 035import org.apache.commons.lang3.StringUtils; 036import org.apache.logging.log4j.LogManager; 037import org.apache.logging.log4j.Logger; 038import org.nuxeo.ecm.core.api.Blob; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.PropertyException; 042import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 043import org.nuxeo.ecm.core.api.model.Property; 044import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 045import org.nuxeo.ecm.core.convert.api.ConversionService; 046import org.nuxeo.ecm.core.schema.types.Type; 047import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 048import org.nuxeo.ecm.core.schema.types.primitives.DateType; 049import org.nuxeo.ecm.core.schema.types.primitives.StringType; 050import org.nuxeo.ecm.platform.rendering.fm.adapters.DocumentObjectWrapper; 051import org.nuxeo.runtime.api.Framework; 052import org.nuxeo.template.api.ContentInputType; 053import org.nuxeo.template.api.InputType; 054import org.nuxeo.template.api.TemplateInput; 055import org.nuxeo.template.api.adapters.TemplateBasedDocument; 056 057import freemarker.template.TemplateModelException; 058 059public abstract class AbstractBindingResolver implements InputBindingResolver { 060 061 private static final Logger log = LogManager.getLogger(AbstractBindingResolver.class); 062 063 protected abstract Object handleLoop(String paramName, Object value); 064 065 protected abstract Object handlePictureField(String paramName, Blob blobValue); 066 067 protected abstract void handleBlobField(String paramName, Blob blobValue); 068 069 protected String handleHtmlField(String paramName, String htmlValue) { 070 return HtmlBodyExtractor.extractHtmlBody(htmlValue); 071 } 072 073 protected DocumentObjectWrapper nuxeoWrapper = new DocumentObjectWrapper(null); 074 075 public AbstractBindingResolver() { 076 super(); 077 } 078 079 protected DocumentObjectWrapper getWrapper() { 080 return nuxeoWrapper; 081 } 082 083 @Override 084 public void resolve(List<TemplateInput> inputParams, Map<String, Object> context, 085 TemplateBasedDocument templateBasedDocument) { 086 for (TemplateInput param : inputParams) { 087 try { 088 Object value = extractValueFromParam(templateBasedDocument, param); 089 context.put(param.getName(), value); 090 } catch (ValueNotFound e) { 091 log.warn("Unable to handle binding for param: {}", param::getName); 092 log.debug(e, e); 093 } catch (NoValueToAddInContext e) { 094 log.warn("Skip param to add: {} ", param::getName); 095 log.debug(e, e); 096 } 097 } 098 } 099 100 protected Object extractValueFromParam(TemplateBasedDocument templateBasedDocument, TemplateInput param) { 101 DocumentModel doc = templateBasedDocument.getAdaptedDoc(); 102 String propKey = param.getSource(); 103 104 switch (param.getType()) { 105 case BooleanValue: 106 return param.getBooleanValue(); 107 case DateValue: 108 return param.getDateValue(); 109 case StringValue: 110 return param.getStringValue(); 111 case MapValue: 112 Map<String, Object> resultMap = new HashMap<>(); 113 param.getMapValue().entrySet().forEach(entry -> { 114 try { 115 resultMap.put(entry.getKey(), extractValueFromParam(templateBasedDocument, entry.getValue())); 116 } catch (NoValueToAddInContext | ValueNotFound e) { 117 log.warn("Skip param to add: {} in: {}", entry::getKey, param::getName); 118 log.debug(e, e); 119 } 120 }); 121 return resultMap; 122 case ListValue: 123 List<Object> resultList = new ArrayList<>(); 124 param.getListValue().forEach(p -> { 125 try { 126 resultList.add(extractValueFromParam(templateBasedDocument, p)); 127 } catch (NoValueToAddInContext | ValueNotFound e) { 128 log.warn("Skip param to add: {} in: {}", p::getName, param::getName); 129 log.debug(e, e); 130 } 131 }); 132 return resultList; 133 case Content: 134 ContentInputType contentInput = ContentInputType.getByValue(param.getSource()); 135 if (BlobContent.equals(contentInput)) { 136 return extractBlobContent(doc, param); 137 } else if (HtmlPreview.equals(contentInput)) { 138 return extractHTMLPreview(doc, param); 139 } else { 140 Serializable docPropertyValue = getDocPropertyValue(doc, propKey); 141 if (docPropertyValue instanceof String) { 142 return handleHtmlField(param.getName(), (String) getDocPropertyValue(doc, propKey)); 143 } 144 } 145 break; 146 case PictureProperty: 147 try { 148 Serializable docPropertyValue = getDocPropertyValue(doc, propKey); 149 if (isBlob(docPropertyValue)) { 150 Blob blob = (Blob) getDocPropertyValue(doc, propKey); 151 addDefaultMimetypeIfRequired(blob); 152 return handlePictureField(param.getName(), blob); 153 } 154 } catch (ValueNotFound e) { 155 return handlePictureField(param.getName(), null); 156 } 157 break; 158 } 159 160 Serializable docPropertyValue = getDocPropertyValue(doc, propKey); 161 if (docPropertyValue == null) { 162 return extractBlobContent(doc, param); 163 } 164 if (isBlob(getDocPropertyValue(doc, propKey))) { 165 throw new NoValueToAddInContext(); 166 } 167 168 Property property = getDocProperty(param, doc); 169 if (param.isAutoLoop()) { 170 return extractAutoLoop(param, property); 171 } else { 172 try { 173 return nuxeoWrapper.wrap(property); 174 } catch (TemplateModelException e) { 175 throw new ValueNotFound(e); 176 } 177 } 178 } 179 180 protected void addDefaultMimetypeIfRequired(Blob blob) { 181 if (StringUtils.isBlank(blob.getMimeType())) { 182 blob.setMimeType("image/jpeg"); 183 } 184 } 185 186 protected Object extractAutoLoop(TemplateInput param, Property property) { 187 // should do the same on all children properties ? 188 return handleLoop(param.getName(), property); 189 } 190 191 protected Object extractBlobContent(DocumentModel doc, TemplateInput param) { 192 Object propValue = getDocPropertyValue(doc, param.getSource()); 193 if (propValue instanceof Blob) { 194 Blob blobValue = (Blob) propValue; 195 handleBlobField(param.getName(), blobValue); 196 try { 197 return blobValue.getString(); 198 } catch (IOException e) { 199 log.warn("Unable to handle binding for param: {}", param.getName(), e); 200 return ""; 201 } 202 } 203 return extractDefaultValue(doc, param); 204 } 205 206 protected String extractHTMLPreview(DocumentModel doc, TemplateInput param) { 207 try { 208 BlobHolder bh = doc.getAdapter(BlobHolder.class); 209 return handleHtmlField(param.getName(), getHtmlValue(bh)); 210 } catch (IOException e) { 211 log.warn("Unable to handle binding for param: {}", param.getName(), e); 212 return null; 213 } 214 } 215 216 protected Object extractDefaultValue(DocumentModel doc, TemplateInput param) { 217 218 // handle special case for pictures 219 if (param.getType().equals(InputType.PictureProperty)) { 220 return handlePictureField(param.getName(), null); 221 } 222 223 try { 224 Property property = doc.getProperty(param.getSource()); 225 226 if (property != null) { 227 Type pType = property.getType(); 228 if (pType.getName().equals(BooleanType.ID)) { 229 return Boolean.FALSE; 230 } else if (pType.getName().equals(DateType.ID)) { 231 return new Date(); 232 } else if (pType.getName().equals(StringType.ID)) { 233 return ""; 234 } else if (pType.getName().equals(Content.getValue())) { 235 return ""; 236 } else { 237 return "!NOVALUE!"; 238 } 239 } 240 } catch (PropertyNotFoundException e) { 241 throw new ValueNotFound(e); 242 } 243 throw new ValueNotFound(); 244 } 245 246 protected Property getDocProperty(TemplateInput param, DocumentModel doc) { 247 Property property; 248 try { 249 property = doc.getProperty(param.getSource()); 250 } catch (PropertyException e) { 251 throw new ValueNotFound(e); 252 } 253 return property; 254 } 255 256 protected Serializable getDocPropertyValue(DocumentModel doc, String propKey) { 257 try { 258 return doc.getPropertyValue(propKey); 259 } catch (PropertyException e) { 260 throw new ValueNotFound(e); 261 } 262 } 263 264 protected boolean isBlob(Serializable propValue) { 265 return propValue != null && Blob.class.isAssignableFrom(propValue.getClass()); 266 } 267 268 protected String getHtmlValue(BlobHolder bh) throws IOException { 269 if (bh == null) { 270 return ""; 271 } 272 273 Blob blob = bh.getBlob(); 274 if (blob != null && "text/html".equals(blob.getMimeType())) { 275 return blob.getString(); 276 } 277 278 ConversionService conversion = Framework.getService(ConversionService.class); 279 BlobHolder htmlBh = conversion.convertToMimeType("text/html", bh, Collections.emptyMap()); 280 if (htmlBh != null) { 281 return htmlBh.getBlob().getString(); 282 } 283 284 if (blob != null && blob.getMimeType() != null && blob.getMimeType().startsWith("text/")) { 285 return blob.getString(); 286 } 287 288 return ""; 289 } 290 291 protected static class ValueNotFound extends NuxeoException { 292 public ValueNotFound(Exception e) { 293 super(e); 294 } 295 296 public ValueNotFound() { 297 } 298 } 299 300 protected static class NoValueToAddInContext extends NuxeoException { 301 } 302 303}