Merge "BUG 932 - Swagger HTTP POST contains incorrect object"
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / xml / codec / InstanceIdentifierForXmlCodec.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.Preconditions;
11 import com.google.common.base.Splitter;
12 import org.opendaylight.yangtools.yang.common.QName;
13 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
14 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
15 import org.opendaylight.yangtools.yang.model.api.Module;
16 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.w3c.dom.Element;
20
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 public final class InstanceIdentifierForXmlCodec {
33   private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]");
34   private static final Splitter SLASH_SPLITTER = Splitter.on('/');
35   private static final Splitter COLON_SPLITTER = Splitter.on(':');
36   private static final Splitter AT_SPLITTER = Splitter.on('@');
37   private static final Logger logger = LoggerFactory.getLogger(InstanceIdentifierForXmlCodec.class);
38
39   private InstanceIdentifierForXmlCodec() {
40     throw new UnsupportedOperationException("Utility class");
41   }
42
43   public static YangInstanceIdentifier deserialize(final Element element, final SchemaContext schemaContext) {
44     Preconditions.checkNotNull(element, "Value of element for deserialization can't be null");
45     Preconditions.checkNotNull(schemaContext,
46         "Schema context for deserialization of instance identifier type can't be null");
47
48     final String valueTrimmed = element.getTextContent().trim();
49     logger.debug("Instance identifier derserialize: splitting the text {} with Slash to find path arguments", valueTrimmed);
50     final Iterator<String> xPathParts = SLASH_SPLITTER.split(valueTrimmed).iterator();
51
52     // must be at least "/pr:node"
53     if (!xPathParts.hasNext() || !xPathParts.next().isEmpty() || !xPathParts.hasNext()) {
54       logger.debug("Instance identifier derserialize: No path argument found for element.");
55       return null;
56     }
57
58     List<PathArgument> result = new ArrayList<>();
59     while (xPathParts.hasNext()) {
60       String xPathPartTrimmed = xPathParts.next().trim();
61
62       PathArgument pathArgument = toPathArgument(xPathPartTrimmed, element, schemaContext);
63       if (pathArgument != null) {
64         result.add(pathArgument);
65       }
66     }
67     return YangInstanceIdentifier.create(result);
68   }
69
70   public static Element serialize(final YangInstanceIdentifier id, final Element element) {
71     Preconditions.checkNotNull(id, "Variable should contain instance of instance identifier and can't be null");
72     Preconditions.checkNotNull(element, "DOM element can't be null");
73
74     final RandomPrefix prefixes = new RandomPrefix();
75     final String str = XmlUtils.encodeIdentifier(prefixes, id);
76
77     for (Entry<URI, String> e: prefixes.getPrefixes()) {
78       element.setAttribute("xmlns:" + e.getValue(), e.getKey().toString());
79     }
80     element.setTextContent(str);
81     return element;
82   }
83
84   private static String getIdAndPrefixAsStr(final String pathPart) {
85     int predicateStartIndex = pathPart.indexOf('[');
86     return predicateStartIndex == -1 ? pathPart : pathPart.substring(0, predicateStartIndex);
87   }
88
89   private static PathArgument toPathArgument(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
90     final QName mainQName = toIdentity(xPathArgument, element, schemaContext);
91
92     // predicates
93     final Matcher matcher = PREDICATE_PATTERN.matcher(xPathArgument);
94     final Map<QName, Object> predicates = new HashMap<>();
95     QName currentQName = mainQName;
96
97     while (matcher.find()) {
98       final String predicateStr = matcher.group(1).trim();
99       final int indexOfEqualityMark = predicateStr.indexOf('=');
100       if (indexOfEqualityMark != -1) {
101         final Object predicateValue = toPredicateValue(predicateStr.substring(indexOfEqualityMark + 1));
102         if (predicateValue == null) {
103           return null;
104         }
105
106         if (predicateStr.charAt(0) != '.') {
107           // target is not a leaf-list
108           currentQName = toIdentity(predicateStr.substring(0, indexOfEqualityMark), element, schemaContext);
109           if (currentQName == null) {
110             return null;
111           }
112         }
113         logger.debug("Instance identifier derserialize: finding predicates of node {}", predicateValue);
114         predicates.put(currentQName, predicateValue);
115       }
116     }
117
118     if (predicates.isEmpty()) {
119       return new YangInstanceIdentifier.NodeIdentifier(mainQName);
120     } else {
121       return new YangInstanceIdentifier.NodeIdentifierWithPredicates(mainQName, predicates);
122     }
123
124   }
125
126   public static QName toIdentity(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
127     final String xPathPartTrimmed = getIdAndPrefixAsStr(xPathArgument).trim();
128     final Iterator<String> it = COLON_SPLITTER.split(xPathPartTrimmed).iterator();
129
130     // Empty string
131     if (!it.hasNext()) {
132       return null;
133     }
134
135     final String prefix = it.next().trim();
136     if (prefix.isEmpty()) {
137       return null;
138     }
139
140     // it is not "prefix:value"
141     if (!it.hasNext()) {
142       return null;
143     }
144
145     final String identifier = it.next().trim();
146     if (identifier.isEmpty()) {
147       return null;
148     }
149
150     URI namespace = null;
151     String namespaceStr = null;
152     try {
153       namespaceStr = element.getAttribute("xmlns:"+prefix);
154       namespace = new URI(namespaceStr);
155     } catch (URISyntaxException e) {
156       throw new IllegalArgumentException("It wasn't possible to convert " + namespaceStr + " to URI object.");
157     } catch (NullPointerException e) {
158       throw new IllegalArgumentException("I wasn't possible to get namespace for prefix " + prefix);
159     }
160
161     Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
162     return QName.create(module.getQNameModule(), identifier);
163   }
164
165   private static String trimIfEndIs(final String str, final char end) {
166     final int l = str.length() - 1;
167     if (str.charAt(l) != end) {
168       return null;
169     }
170
171     return str.substring(1, l);
172   }
173
174   private static Object toPredicateValue(final String predicatedValue) {
175     logger.debug("Instance identifier derserialize: converting the predicate vstring to object {}", predicatedValue);
176     final String predicatedValueTrimmed = predicatedValue.trim();
177     if (predicatedValue.isEmpty()) {
178       return null;
179     }
180     String updatedValue = null;
181     switch (predicatedValueTrimmed.charAt(0)) {
182       case '"':
183         updatedValue =  trimIfEndIs(predicatedValueTrimmed, '"');
184         break;
185       case '\'':
186         updatedValue =  trimIfEndIs(predicatedValueTrimmed, '\'');
187         break;
188       default:
189         updatedValue =  predicatedValueTrimmed;
190     }
191     Iterator<String> it = AT_SPLITTER.split(updatedValue).iterator();
192     // Empty string
193     if (!it.hasNext()) {
194       return null;
195     }
196
197     final String value = it.next().trim();
198     if (value.isEmpty()) {
199       return null;
200     }
201
202     if (!it.hasNext()) {
203       return value;
204     }
205
206     final String type = it.next().trim();
207     if (type.isEmpty()) {
208       return value;
209     }
210     Object predicateObject = null;
211     try {
212       logger.debug("Instance identifier derserialize: converting the predicate value {{}}to correct object type {{}}", value, type);
213       predicateObject = Class.forName(type).getConstructor(String.class).newInstance(value);
214     } catch (Exception e) {
215       logger.error("Could not convert to valid type of value", e);
216     }
217     return predicateObject;
218   }
219 }