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}