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}