Merge "Bug 2517: Catch RuntimeExceptions thrown from the DCL in DataChangeListener"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestCodec.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.sal.restconf.impl;
9
10 import java.net.URI;
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
16 import org.opendaylight.controller.sal.rest.impl.RestUtil;
17 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
18 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO.Predicate;
19 import org.opendaylight.yangtools.concepts.Codec;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec;
27 import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
28 import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
29 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
30 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
31 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
36 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
37 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
38 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 public class RestCodec {
43
44     private static final Logger logger = LoggerFactory.getLogger(RestCodec.class);
45
46     private RestCodec() {
47     }
48
49     public static final Codec<Object, Object> from(final TypeDefinition<?> typeDefinition,
50             final DOMMountPoint mountPoint) {
51         return new ObjectCodec(typeDefinition, mountPoint);
52     }
53
54     @SuppressWarnings("rawtypes")
55     public static final class ObjectCodec implements Codec<Object, Object> {
56
57         private final Logger logger = LoggerFactory.getLogger(RestCodec.class);
58
59         public static final Codec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();
60         private final Codec instanceIdentifier;
61         private final Codec identityrefCodec;
62
63         private final TypeDefinition<?> type;
64
65         private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint) {
66             type = RestUtil.resolveBaseTypeFrom(typeDefinition);
67             if (type instanceof IdentityrefTypeDefinition) {
68                 identityrefCodec = new IdentityrefCodecImpl(mountPoint);
69             } else {
70                 identityrefCodec = null;
71             }
72             if (type instanceof InstanceIdentifierTypeDefinition) {
73                 instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint);
74             } else {
75                 instanceIdentifier = null;
76             }
77         }
78
79         @SuppressWarnings("unchecked")
80         @Override
81         public Object deserialize(final Object input) {
82             try {
83                 if (type instanceof IdentityrefTypeDefinition) {
84                     if (input instanceof IdentityValuesDTO) {
85                         return identityrefCodec.deserialize(input);
86                     }
87                     logger.debug(
88                             "Value is not instance of IdentityrefTypeDefinition but is {}. Therefore NULL is used as translation of  - {}",
89                             input == null ? "null" : input.getClass(), String.valueOf(input));
90                     return null;
91                 } else if (type instanceof InstanceIdentifierTypeDefinition) {
92                     if (input instanceof IdentityValuesDTO) {
93                         return instanceIdentifier.deserialize(input);
94                     }
95                     logger.info(
96                             "Value is not instance of InstanceIdentifierTypeDefinition but is {}. Therefore NULL is used as translation of  - {}",
97                             input == null ? "null" : input.getClass(), String.valueOf(input));
98                     return null;
99                 } else {
100                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
101                             .from(type);
102                     if (typeAwarecodec != null) {
103                         if (input instanceof IdentityValuesDTO) {
104                             return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
105                         }
106                         return typeAwarecodec.deserialize(String.valueOf(input));
107                     } else {
108                         logger.debug("Codec for type \"" + type.getQName().getLocalName()
109                                 + "\" is not implemented yet.");
110                         return null;
111                     }
112                 }
113             } catch (final ClassCastException e) { // TODO remove this catch when everyone use codecs
114                 logger.error(
115                         "ClassCastException was thrown when codec is invoked with parameter " + String.valueOf(input),
116                         e);
117                 return null;
118             }
119         }
120
121         @SuppressWarnings("unchecked")
122         @Override
123         public Object serialize(final Object input) {
124             try {
125                 if (type instanceof IdentityrefTypeDefinition) {
126                     return identityrefCodec.serialize(input);
127                 } else if (type instanceof LeafrefTypeDefinition) {
128                     return LEAFREF_DEFAULT_CODEC.serialize(input);
129                 } else if (type instanceof InstanceIdentifierTypeDefinition) {
130                     return instanceIdentifier.serialize(input);
131                 } else {
132                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
133                             .from(type);
134                     if (typeAwarecodec != null) {
135                         return typeAwarecodec.serialize(input);
136                     } else {
137                         logger.debug("Codec for type \"" + type.getQName().getLocalName()
138                                 + "\" is not implemented yet.");
139                         return null;
140                     }
141                 }
142             } catch (final ClassCastException e) { // TODO remove this catch when everyone use codecs
143                 logger.error(
144                         "ClassCastException was thrown when codec is invoked with parameter " + String.valueOf(input),
145                         e);
146                 return input;
147             }
148         }
149
150     }
151
152     public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
153
154         private final Logger logger = LoggerFactory.getLogger(IdentityrefCodecImpl.class);
155
156         private final DOMMountPoint mountPoint;
157
158         public IdentityrefCodecImpl(final DOMMountPoint mountPoint) {
159             this.mountPoint = mountPoint;
160         }
161
162         @Override
163         public IdentityValuesDTO serialize(final QName data) {
164             return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
165         }
166
167         @Override
168         public QName deserialize(final IdentityValuesDTO data) {
169             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
170             final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
171             if (module == null) {
172                 logger.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
173                 logger.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
174                 return null;
175             }
176
177             return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue());
178         }
179
180     }
181
182     public static class LeafrefCodecImpl implements LeafrefCodec<String> {
183
184         @Override
185         public String serialize(final Object data) {
186             return String.valueOf(data);
187         }
188
189         @Override
190         public Object deserialize(final String data) {
191             return data;
192         }
193
194     }
195
196     public static class InstanceIdentifierCodecImpl implements InstanceIdentifierCodec<IdentityValuesDTO> {
197         private final Logger logger = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class);
198         private final DOMMountPoint mountPoint;
199
200         public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint) {
201             this.mountPoint = mountPoint;
202         }
203
204         @Override
205         public IdentityValuesDTO serialize(final YangInstanceIdentifier data) {
206             final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
207             for (final PathArgument pathArgument : data.getPathArguments()) {
208                 final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
209                 if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
210                     final List<Predicate> predicates = keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument)
211                             .getKeyValues());
212                     identityValue.setPredicates(predicates);
213                 } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
214                     final List<Predicate> predicates = new ArrayList<>();
215                     final String value = String.valueOf(((NodeWithValue) pathArgument).getValue());
216                     predicates.add(new Predicate(null, value));
217                     identityValue.setPredicates(predicates);
218                 }
219                 identityValuesDTO.add(identityValue);
220             }
221             return identityValuesDTO;
222         }
223
224         @Override
225         public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
226             final List<PathArgument> result = new ArrayList<PathArgument>();
227             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
228             final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
229             if (module == null) {
230                 logger.info("Module by namespace '{}' of first node in instance-identifier was not found.",
231                         valueWithNamespace.getNamespace());
232                 logger.info("Instance-identifier will be translated as NULL for data - {}",
233                         String.valueOf(valueWithNamespace.getValue()));
234                 return null;
235             }
236
237             DataNodeContainer parentContainer = module;
238             final List<IdentityValue> identities = data.getValuesWithNamespaces();
239             for (int i = 0; i < identities.size(); i++) {
240                 final IdentityValue identityValue = identities.get(i);
241                 URI validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint);
242                 final DataSchemaNode node = ControllerContext.findInstanceDataChildByNameAndNamespace(
243                         parentContainer, identityValue.getValue(), validNamespace);
244                 if (node == null) {
245                     logger.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes());
246                     logger.info("Instance-identifier will be translated as NULL for data - {}",
247                             String.valueOf(identityValue.getValue()));
248                     return null;
249                 }
250                 final QName qName = node.getQName();
251                 PathArgument pathArgument = null;
252                 if (identityValue.getPredicates().isEmpty()) {
253                     pathArgument = new NodeIdentifier(qName);
254                 } else {
255                     if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry
256                         final Predicate leafListPredicate = identityValue.getPredicates().get(0);
257                         if (!leafListPredicate.isLeafList()) {
258                             logger.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\"");
259                             logger.info("Instance-identifier will be translated as NULL for data - {}",
260                                     String.valueOf(identityValue.getValue()));
261                             return null;
262                         }
263                         pathArgument = new NodeWithValue(qName, leafListPredicate.getValue());
264                     } else if (node instanceof ListSchemaNode) { // predicates are keys of list
265                         final DataNodeContainer listNode = (DataNodeContainer) node;
266                         final Map<QName, Object> predicatesMap = new HashMap<>();
267                         for (final Predicate predicate : identityValue.getPredicates()) {
268                             validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint);
269                             final DataSchemaNode listKey = ControllerContext
270                                     .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
271                                             validNamespace);
272                             predicatesMap.put(listKey.getQName(), predicate.getValue());
273                         }
274                         pathArgument = new NodeIdentifierWithPredicates(qName, predicatesMap);
275                     } else {
276                         logger.info("Node {} is not List or Leaf-list.", node);
277                         logger.info("Instance-identifier will be translated as NULL for data - {}",
278                                 String.valueOf(identityValue.getValue()));
279                         return null;
280                     }
281                 }
282                 result.add(pathArgument);
283                 if (i < identities.size() - 1) { // last element in instance-identifier can be other than
284                     // DataNodeContainer
285                     if (node instanceof DataNodeContainer) {
286                         parentContainer = (DataNodeContainer) node;
287                     } else {
288                         logger.info("Node {} isn't instance of DataNodeContainer", node);
289                         logger.info("Instance-identifier will be translated as NULL for data - {}",
290                                 String.valueOf(identityValue.getValue()));
291                         return null;
292                     }
293                 }
294             }
295
296             return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
297         }
298
299         private List<Predicate> keyValuesToPredicateList(final Map<QName, Object> keyValues) {
300             final List<Predicate> result = new ArrayList<>();
301             for (final QName qName : keyValues.keySet()) {
302                 final Object value = keyValues.get(qName);
303                 result.add(new Predicate(qNameToIdentityValue(qName), String.valueOf(value)));
304             }
305             return result;
306         }
307
308         private IdentityValue qNameToIdentityValue(final QName qName) {
309             if (qName != null) {
310                 return new IdentityValue(qName.getNamespace().toString(), qName.getLocalName());
311             }
312             return null;
313         }
314     }
315
316     private static Module getModuleByNamespace(final String namespace, final DOMMountPoint mountPoint) {
317         final URI validNamespace = resolveValidNamespace(namespace, mountPoint);
318
319         Module module = null;
320         if (mountPoint != null) {
321             module = ControllerContext.getInstance().findModuleByNamespace(mountPoint, validNamespace);
322         } else {
323             module = ControllerContext.getInstance().findModuleByNamespace(validNamespace);
324         }
325         if (module == null) {
326             logger.info("Module for namespace " + validNamespace + " wasn't found.");
327             return null;
328         }
329         return module;
330     }
331
332     private static URI resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint) {
333         URI validNamespace;
334         if (mountPoint != null) {
335             validNamespace = ControllerContext.getInstance().findNamespaceByModuleName(mountPoint, namespace);
336         } else {
337             validNamespace = ControllerContext.getInstance().findNamespaceByModuleName(namespace);
338         }
339         if (validNamespace == null) {
340             validNamespace = URI.create(namespace);
341         }
342
343         return validNamespace;
344     }
345
346 }