Merge "BUG-1690: catch wildcard InstanceIdentifiers"
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / xml / codec / XmlUtils.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.xml.codec;
9
10 import com.google.common.base.Optional;
11 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
12 import org.opendaylight.yangtools.yang.common.QName;
13 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
14 import org.opendaylight.yangtools.yang.data.api.Node;
15 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
16 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
18 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
20 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
21 import org.opendaylight.yangtools.yang.data.impl.XmlTreeBuilder;
22 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
23 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlCodecProvider;
24 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
25 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.NodeList;
33 import org.xml.sax.SAXException;
34
35 import javax.activation.UnsupportedDataTypeException;
36 import javax.annotation.Nonnull;
37 import javax.xml.stream.XMLStreamException;
38 import javax.xml.transform.OutputKeys;
39 import javax.xml.transform.Transformer;
40 import javax.xml.transform.TransformerException;
41 import javax.xml.transform.TransformerFactory;
42 import javax.xml.transform.dom.DOMSource;
43 import javax.xml.transform.stream.StreamResult;
44 import java.io.ByteArrayInputStream;
45 import java.io.IOException;
46 import java.io.StringWriter;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50
51 /**
52  * Common XML-related utility methods, which are not specific to a particular
53  * JAXP API.
54  */
55 public class XmlUtils {
56
57   public static final XmlCodecProvider DEFAULT_XML_CODEC_PROVIDER = new XmlCodecProvider() {
58     @Override
59     public TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> codecFor(final TypeDefinition<?> baseType) {
60       return TypeDefinitionAwareCodec.from(baseType);
61     }
62   };
63
64   private XmlUtils() {
65   }
66
67   private static final String BLANK = "";
68   private static final Logger LOG = LoggerFactory.getLogger(XmlUtils.class);
69
70   /**
71    * Converts the composite node to xml using rpc input schema node
72    * @param cNode
73    * @param schemaContext
74    * @return xml String
75    */
76   public static String inputCompositeNodeToXml(CompositeNode cNode, SchemaContext schemaContext){
77     LOG.debug("Converting input composite node to xml {}", cNode);
78     if (cNode == null) {
79         return BLANK;
80     }
81
82     if(schemaContext == null) {
83         return BLANK;
84     }
85
86     Document domTree = null;
87     try {
88       Set<RpcDefinition> rpcs =  schemaContext.getOperations();
89       for(RpcDefinition rpc : rpcs) {
90         if(rpc.getQName().equals(cNode.getNodeType())){
91           LOG.debug("Found the rpc definition from schema context matching with input composite node  {}", rpc.getQName());
92
93           CompositeNode inputContainer = cNode.getFirstCompositeByName(QName.create(cNode.getNodeType(), "input"));
94           domTree = XmlDocumentUtils.toDocument(inputContainer, rpc.getInput(), XmlDocumentUtils.defaultValueCodecProvider());
95
96           LOG.debug("input composite node to document conversion complete, document is   {}", domTree);
97           break;
98         }
99       }
100
101     } catch (UnsupportedDataTypeException e) {
102       LOG.error("Error during translation of CompositeNode to Document", e);
103     }
104     return domTransformer(domTree);
105   }
106
107   /**
108    * Converts the composite node to xml String using rpc output schema node
109    * @param cNode
110    * @param schemaContext
111    * @return xml string
112    */
113   public static String outputCompositeNodeToXml(CompositeNode cNode, SchemaContext schemaContext){
114     LOG.debug("Converting output composite node to xml {}", cNode);
115     if (cNode == null) {
116         return BLANK;
117     }
118
119     if(schemaContext == null) {
120         return BLANK;
121     }
122
123     Document domTree = null;
124     try {
125       Set<RpcDefinition> rpcs =  schemaContext.getOperations();
126       for(RpcDefinition rpc : rpcs) {
127         if(rpc.getQName().equals(cNode.getNodeType())){
128           LOG.debug("Found the rpc definition from schema context matching with output composite node  {}", rpc.getQName());
129
130           CompositeNode outputContainer = cNode.getFirstCompositeByName(QName.create(cNode.getNodeType(), "output"));
131           domTree = XmlDocumentUtils.toDocument(outputContainer, rpc.getOutput(), XmlDocumentUtils.defaultValueCodecProvider());
132
133           LOG.debug("output composite node to document conversion complete, document is   {}", domTree);
134           break;
135         }
136       }
137
138     } catch (UnsupportedDataTypeException e) {
139       LOG.error("Error during translation of CompositeNode to Document", e);
140     }
141     return domTransformer(domTree);
142   }
143
144   private static String domTransformer(Document domTree) {
145     StringWriter writer = new StringWriter();
146     try {
147       TransformerFactory tf = TransformerFactory.newInstance();
148       Transformer transformer = tf.newTransformer();
149       transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
150       transformer.transform(new DOMSource(domTree), new StreamResult(writer));
151     } catch (TransformerException e) {
152
153       LOG.error("Error during translation of Document to OutputStream", e);
154     }
155     LOG.debug("Document to string conversion complete, xml string is  {} ",  writer.toString());
156
157     return writer.toString();
158   }
159
160   public static CompositeNode xmlToCompositeNode(String xml){
161     if (xml==null || xml.length()==0) {
162         return null;
163     }
164
165     Node<?> dataTree;
166     try {
167       dataTree = XmlTreeBuilder.buildDataTree(new ByteArrayInputStream(xml.getBytes()));
168     } catch (XMLStreamException e) {
169       LOG.error("Error during building data tree from XML", e);
170       return null;
171     }
172     if (dataTree == null) {
173       LOG.error("data tree is null");
174       return null;
175     }
176     if (dataTree instanceof SimpleNode) {
177       LOG.error("RPC XML was resolved as SimpleNode");
178       return null;
179     }
180     return (CompositeNode) dataTree;
181   }
182
183   /**
184    * Converts the xml to composite node using rpc input schema node
185    * @param rpc
186    * @param xml
187    * @param schemaContext
188    * @return CompositeNode object based on the input, if any of the input parameter is null, a null object is returned
189    */
190   public static CompositeNode inputXmlToCompositeNode(QName rpc, String xml,  SchemaContext schemaContext){
191     LOG.debug("Converting input xml to composite node {}", xml);
192     if (xml==null || xml.length()==0) {
193         return null;
194     }
195
196     if(rpc == null) {
197         return null;
198     }
199
200     if(schemaContext == null) {
201         return null;
202     }
203
204     CompositeNode compositeNode = null;
205     try {
206
207       Document doc = XmlUtil.readXmlToDocument(xml);
208       Set<RpcDefinition> rpcs =  schemaContext.getOperations();
209       for(RpcDefinition rpcDef : rpcs) {
210         if(rpcDef.getQName().equals(rpc)){
211           LOG.debug("found the rpc definition from schema context matching rpc  {}", rpc);
212
213           if(rpcDef.getInput() == null) {
214             LOG.warn("found rpc definition's input is null");
215             return null;
216           }
217
218           QName input = rpcDef.getInput().getQName();
219           NodeList nodeList = doc.getElementsByTagNameNS(input.getNamespace().toString(), "input");
220           if(nodeList == null || nodeList.getLength() < 1) {
221             LOG.warn("xml does not have input entry. {}", xml);
222             return null;
223           }
224           Element xmlData = (Element)nodeList.item(0);
225
226           List<Node<?>> dataNodes = XmlDocumentUtils.toDomNodes(xmlData,
227               Optional.of(rpcDef.getInput().getChildNodes()), schemaContext);
228
229           LOG.debug("Converted xml input to list of nodes  {}", dataNodes);
230
231           final CompositeNodeBuilder<ImmutableCompositeNode> it = ImmutableCompositeNode.builder();
232           it.setQName(rpc);
233           it.add(ImmutableCompositeNode.create(input, dataNodes));
234           compositeNode = it.toInstance();
235           break;
236         }
237       }
238     } catch (SAXException e) {
239       LOG.error("Error during building data tree from XML", e);
240     } catch (IOException e) {
241       LOG.error("Error during building data tree from XML", e);
242     }
243
244     LOG.debug("Xml to composite node conversion complete {} ", compositeNode);
245     return compositeNode;
246   }
247
248   public static TypeDefinition<?> resolveBaseTypeFrom(final @Nonnull TypeDefinition<?> type) {
249     TypeDefinition<?> superType = type;
250     while (superType.getBaseType() != null) {
251       superType = superType.getBaseType();
252     }
253     return superType;
254   }
255
256   /**
257    * This code is picked from yangtools and modified to add type of instance identifier
258    * output of instance identifier something like below for a flow ref composite node of type instance identifier,
259    * which has path arguments with predicates, whose value is of type java.lang.short
260    * <flow-ref xmlns:bgkj="urn:opendaylight:flow:inventory" xmlns:jdlk="urn:opendaylight:inventory">
261    *   /jdlk:nodes/jdlk:node[jdlk:id='openflow:205558455098190@java.lang.String']
262    *   /bgkj:table[bgkj:id='3@java.lang.Short']
263    *   /bgkj:flow[bgkj:id='156@java.lang.String']
264    * </flow-ref>
265    *
266    */
267
268   public static String encodeIdentifier(final RandomPrefix prefixes, final YangInstanceIdentifier id) {
269     StringBuilder textContent = new StringBuilder();
270     for (PathArgument pathArgument : id.getPathArguments()) {
271       textContent.append('/');
272       textContent.append(prefixes.encodeQName(pathArgument.getNodeType()));
273       if (pathArgument instanceof NodeIdentifierWithPredicates) {
274         Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
275
276         for (QName keyValue : predicates.keySet()) {
277           Object value = predicates.get(keyValue);
278           String type = value.getClass().getName();
279           String predicateValue = String.valueOf(value);
280           textContent.append('[');
281           textContent.append(prefixes.encodeQName(keyValue));
282           textContent.append("='");
283           textContent.append(predicateValue);
284           textContent.append("@");
285           textContent.append(type);
286           textContent.append("']");
287         }
288       } else if (pathArgument instanceof NodeWithValue) {
289         textContent.append("[.='");
290         textContent.append(((NodeWithValue) pathArgument).getValue());
291         textContent.append("']");
292       }
293     }
294
295     return textContent.toString();
296   }
297 }