001/*
002 * (C) Copyright 2019 Nuxeo (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 *     Benjamin JALON
018 *     Thierry Delprat
019 *
020 */
021
022package org.nuxeo.template.serializer.executors;
023
024import static org.nuxeo.template.api.InputType.MapValue;
025import static org.nuxeo.template.api.InputType.StringValue;
026
027import java.text.ParseException;
028import java.text.SimpleDateFormat;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.stream.Collectors;
033
034import org.apache.logging.log4j.LogManager;
035import org.apache.logging.log4j.Logger;
036import org.dom4j.Attribute;
037import org.dom4j.Document;
038import org.dom4j.DocumentException;
039import org.dom4j.DocumentFactory;
040import org.dom4j.DocumentHelper;
041import org.dom4j.Element;
042import org.dom4j.Namespace;
043import org.dom4j.QName;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.NuxeoException;
046import org.nuxeo.template.api.InputType;
047import org.nuxeo.template.api.TemplateInput;
048
049/**
050 * {@link TemplateInput} parameters are stored in the {@link DocumentModel} as a single String Property via XML
051 * Serialization. This class contains the Serialization/Deserialization logic.
052 *
053 * @author Tiry (tdelprat@nuxeo.com)
054 * @author bjalon (bjalon@qastia.com)
055 * @since 11.1
056 */
057public class XMLTemplateSerializer implements TemplateSerializer {
058
059    public static final String XML_NAMESPACE = "http://www.nuxeo.org/DocumentTemplate";
060
061    public static final String XML_NAMESPACE_PREFIX = "nxdt";
062
063    public static final Namespace ns = new Namespace(XML_NAMESPACE_PREFIX, XML_NAMESPACE);
064
065    public static final QName fieldsTag = DocumentFactory.getInstance().createQName("templateParams", ns);
066
067    public static final QName fieldTag = DocumentFactory.getInstance().createQName("field", ns);
068
069    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
070
071    private static final Logger log = LogManager.getLogger(XMLTemplateSerializer.class);
072
073    @Override
074    public List<TemplateInput> deserialize(String xml) {
075        try {
076            Document xmlDoc = DocumentHelper.parseText(xml);
077            List<Element> nodes = xmlDoc.getRootElement().elements(fieldTag);
078            return nodes.stream().map(this::extractTemplateInputFromXMLNode).collect(Collectors.toList());
079        } catch (DocumentException e) {
080            throw new NuxeoException(e);
081        }
082    }
083
084    protected TemplateInput extractTemplateInputFromXMLNode(Element node) {
085        String paramName = getNameFromXMLNode(node);
086        InputType paramType = getTypeFromXMLNode(node);
087        String paramDesc = node.getText();
088        Boolean isReadonly = getIsReadonlyFromXMLNode(node);
089        Boolean isAutoloop = getIsAutoloopFromXMLNode(node);
090
091        Object paramValue = node.attributeValue("value");
092        switch (paramType) {
093        case StringValue:
094        case BooleanValue:
095            break;
096        case DateValue:
097            SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
098            try {
099                paramValue = dateFormat.parse((String) paramValue);
100            } catch (ParseException e) {
101                throw new NuxeoException(e);
102            }
103            break;
104        case MapValue:
105        case ListValue:
106            Map<String, TemplateInput> listValue = new LinkedHashMap<>();
107            for (Element childNode : node.elements()) {
108                TemplateInput childParam = extractTemplateInputFromXMLNode(childNode);
109                if (childNode != null) {
110                    listValue.put(childParam.getName(), childParam);
111                }
112            }
113            paramValue = listValue;
114            break;
115        default:
116            paramValue = node.attributeValue("source");
117        }
118        return TemplateInput.factory(paramName, paramType, paramValue, paramDesc, isReadonly, isAutoloop);
119    }
120
121    protected Boolean getIsReadonlyFromXMLNode(Element elem) {
122        Attribute readonly = elem.attribute("readonly");
123        return readonly != null ? Boolean.parseBoolean(readonly.getValue()) : null;
124    }
125
126    protected Boolean getIsAutoloopFromXMLNode(Element elem) {
127        Attribute autoloop = elem.attribute("autoloop");
128        return autoloop != null ? Boolean.parseBoolean(autoloop.getValue()) : null;
129    }
130
131    protected String getNameFromXMLNode(Element elem) {
132        Attribute name = elem.attribute("name");
133        return name != null ? name.getValue() : null;
134    }
135
136    protected InputType getTypeFromXMLNode(Element elem) {
137        InputType type = null;
138        Attribute typeAtt = elem.attribute("type");
139        if (typeAtt != null) {
140            type = InputType.getByValue(typeAtt.getValue());
141        }
142
143        return type == null ? StringValue : type;
144    }
145
146    @Override
147    public String serialize(List<TemplateInput> params) {
148        Element root = DocumentFactory.getInstance().createElement(fieldsTag);
149
150        for (TemplateInput param : params) {
151            Element field = root.addElement(fieldTag);
152            try {
153                doSerialization(field, param);
154            } catch (TemplateInputBadFormat e) {
155                log.error("Can't Serialize the following param: {}", param);
156                root.remove(field);
157            }
158        }
159        return root.asXML();
160    }
161
162    protected void doSerialization(Element field, TemplateInput param) {
163
164        field.addAttribute("name", param.getName());
165
166        InputType type = param.getType();
167
168        if (type == null) {
169            if (param.getStringValue() == null) {
170                log.warn("Null param: {}", param::getName);
171                throw new TemplateInputBadFormat();
172            } else
173                type = StringValue;
174        }
175
176        field.addAttribute("type", type.getValue());
177        if (param.isReadOnly()) {
178            field.addAttribute("readonly", "true");
179        }
180        if (param.isAutoLoop()) {
181            field.addAttribute("autoloop", "true");
182        }
183        if (param.getDesciption() != null) {
184            field.setText(param.getDesciption());
185        }
186
187        switch (type) {
188        case StringValue:
189            field.addAttribute("value", param.getStringValue());
190            break;
191        case DateValue:
192            SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
193            field.addAttribute("value", dateFormat.format(param.getDateValue()));
194            break;
195        case BooleanValue:
196            field.addAttribute("value", param.getBooleanValue().toString());
197            break;
198        case MapValue:
199        case ListValue:
200            Map<String, TemplateInput> map = param.getMapValue();
201            for (String childParamName : map.keySet()) {
202                TemplateInput childParam = map.get(childParamName);
203                if (MapValue.equals(type) && !childParamName.equals(childParam.getName())) {
204                    log.warn("Child param in map and child param name doesn't match, get child param name as key: {}",
205                            childParam);
206                }
207                Element subfield = field.addElement(fieldTag);
208                doSerialization(subfield, childParam);
209            }
210            break;
211        case DocumentProperty:
212        case PictureProperty:
213        case Content:
214            field.addAttribute("source", param.getSource());
215            break;
216        }
217    }
218
219    protected static class TemplateInputBadFormat extends NuxeoException {
220    }
221}