Bump upstreams to SNAPSHOTs
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / codecs / 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.restconf.nb.rfc8040.codecs;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.Iterables;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Optional;
21 import java.util.Set;
22 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
23 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
24 import org.opendaylight.restconf.common.util.IdentityValuesDTO;
25 import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
26 import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
27 import org.opendaylight.restconf.common.util.RestUtil;
28 import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.common.XMLNamespace;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
36 import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec;
37 import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
38 import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
39 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
40 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
45 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
47 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.Module;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
52 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 // FIXME: what is this class even trying to do?
60 public final class RestCodec {
61     private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class);
62
63     private RestCodec() {
64         // Hidden on purpose
65     }
66
67     // FIXME: IllegalArgumentCodec is not quite accurate
68     public static IllegalArgumentCodec<Object, Object> from(final TypeDefinition<?> typeDefinition,
69             final DOMMountPoint mountPoint, final EffectiveModelContext schemaContext) {
70         return new ObjectCodec(typeDefinition, mountPoint, schemaContext);
71     }
72
73     @SuppressWarnings("rawtypes")
74     public static final class ObjectCodec implements IllegalArgumentCodec<Object, Object> {
75
76         private static final Logger LOG = LoggerFactory.getLogger(ObjectCodec.class);
77
78         public static final IllegalArgumentCodec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();
79         private final IllegalArgumentCodec instanceIdentifier;
80         private final IllegalArgumentCodec identityrefCodec;
81
82         private final TypeDefinition<?> type;
83
84         private final EffectiveModelContext schemaContext;
85
86         private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint,
87                 final EffectiveModelContext schemaContext) {
88             this.schemaContext = schemaContext;
89             type = RestUtil.resolveBaseTypeFrom(typeDefinition);
90             if (type instanceof IdentityrefTypeDefinition) {
91                 identityrefCodec = new IdentityrefCodecImpl(mountPoint, schemaContext);
92             } else {
93                 identityrefCodec = null;
94             }
95             if (type instanceof InstanceIdentifierTypeDefinition) {
96                 instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint, schemaContext);
97             } else {
98                 instanceIdentifier = null;
99             }
100         }
101
102         @SuppressWarnings("unchecked")
103         @Override
104         @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy returns")
105         public Object deserialize(final Object input) {
106             try {
107                 if (type instanceof IdentityrefTypeDefinition) {
108                     if (input instanceof IdentityValuesDTO) {
109                         return identityrefCodec.deserialize(input);
110                     }
111                     if (LOG.isDebugEnabled()) {
112                         LOG.debug(
113                             "Value is not instance of IdentityrefTypeDefinition but is {}. "
114                                     + "Therefore NULL is used as translation of - {}",
115                             input == null ? "null" : input.getClass(), String.valueOf(input));
116                     }
117                     // FIXME: this should be a hard error
118                     return null;
119                 } else if (type instanceof InstanceIdentifierTypeDefinition) {
120                     return input instanceof IdentityValuesDTO ? instanceIdentifier.deserialize(input)
121                         // FIXME: what is it that we are trying to decode here and why?
122                         : new StringModuleInstanceIdentifierCodec(schemaContext).deserialize((String) input);
123                 } else {
124                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
125                             TypeDefinitionAwareCodec.from(type);
126                     if (typeAwarecodec != null) {
127                         if (input instanceof IdentityValuesDTO) {
128                             return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
129                         }
130                         return typeAwarecodec.deserialize(String.valueOf(input));
131                     } else {
132                         // FIXME: this should be a hard error
133                         LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
134                         return null;
135                     }
136                 }
137             } catch (final ClassCastException e) {
138                 // FIXME: remove this catch when everyone use codecs
139                 // FIXME: this should be a hard error
140                 LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
141                 return null;
142             }
143         }
144
145         @SuppressWarnings("unchecked")
146         @Override
147         @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy returns")
148         public Object serialize(final Object input) {
149             try {
150                 if (type instanceof IdentityrefTypeDefinition) {
151                     return identityrefCodec.serialize(input);
152                 } else if (type instanceof LeafrefTypeDefinition) {
153                     return LEAFREF_DEFAULT_CODEC.serialize(input);
154                 } else if (type instanceof InstanceIdentifierTypeDefinition) {
155                     return instanceIdentifier.serialize(input);
156                 } else {
157                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
158                             TypeDefinitionAwareCodec.from(type);
159                     if (typeAwarecodec != null) {
160                         return typeAwarecodec.serialize(input);
161                     } else {
162                         // FIXME: this needs to be a hard error
163                         if (LOG.isDebugEnabled()) {
164                             LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
165                         }
166                         return null;
167                     }
168                 }
169             } catch (final ClassCastException e) {
170                 // FIXME: remove this catch when everyone use codecs
171                 // FIXME: this needs to be a hard error
172                 LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
173                 return input;
174             }
175         }
176     }
177
178     public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
179
180         private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class);
181
182         private final DOMMountPoint mountPoint;
183
184         private final SchemaContext schemaContext;
185
186         public IdentityrefCodecImpl(final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
187             this.mountPoint = mountPoint;
188             this.schemaContext = schemaContext;
189         }
190
191         @Override
192         public IdentityValuesDTO serialize(final QName data) {
193             return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
194         }
195
196         @Override
197         @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy return")
198         public QName deserialize(final IdentityValuesDTO data) {
199             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
200             final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint, schemaContext);
201             // FIXME: this needs to be a hard error
202             if (module == null) {
203                 LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
204                 LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
205                 return null;
206             }
207
208             return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue());
209         }
210
211     }
212
213     public static class LeafrefCodecImpl implements LeafrefCodec<String> {
214
215         @Override
216         public String serialize(final Object data) {
217             return String.valueOf(data);
218         }
219
220         @Override
221         public Object deserialize(final String data) {
222             return data;
223         }
224
225     }
226
227     public static class InstanceIdentifierCodecImpl implements InstanceIdentifierCodec<IdentityValuesDTO> {
228         private static final Logger LOG = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class);
229         private final DOMMountPoint mountPoint;
230         private final SchemaContext schemaContext;
231
232         public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint, final SchemaContext schemaContext) {
233             this.mountPoint = mountPoint;
234             this.schemaContext = schemaContext;
235         }
236
237         @Override
238         public IdentityValuesDTO serialize(final YangInstanceIdentifier data) {
239             final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
240             for (final PathArgument pathArgument : data.getPathArguments()) {
241                 final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
242                 if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
243                     final List<Predicate> predicates =
244                             keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument).entrySet());
245                     identityValue.setPredicates(predicates);
246                 } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
247                     final List<Predicate> predicates = new ArrayList<>();
248                     final String value = String.valueOf(((NodeWithValue<?>) pathArgument).getValue());
249                     predicates.add(new Predicate(null, value));
250                     identityValue.setPredicates(predicates);
251                 }
252                 identityValuesDTO.add(identityValue);
253             }
254             return identityValuesDTO;
255         }
256
257         @SuppressFBWarnings(value = {
258             "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE",
259             "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE",
260             "NP_NONNULL_RETURN_VIOLATION"
261         }, justification = "Unrecognised NullableDecl")
262         @Override
263         public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
264             final List<PathArgument> result = new ArrayList<>();
265             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
266             final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint, schemaContext);
267             // FIXME: this needs to be a hard error
268             if (module == null) {
269                 LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
270                         valueWithNamespace.getNamespace());
271                 LOG.info("Instance-identifier will be translated as NULL for data - {}",
272                         String.valueOf(valueWithNamespace.getValue()));
273                 return null;
274             }
275
276             DataNodeContainer parentContainer = module;
277             final List<IdentityValue> identities = data.getValuesWithNamespaces();
278             for (int i = 0; i < identities.size(); i++) {
279                 final IdentityValue identityValue = identities.get(i);
280                 XMLNamespace validNamespace =
281                         resolveValidNamespace(identityValue.getNamespace(), mountPoint, schemaContext);
282                 final DataSchemaNode node = findInstanceDataChildByNameAndNamespace(
283                         parentContainer, identityValue.getValue(), validNamespace);
284                 // FIXME: this needs to be a hard error
285                 if (node == null) {
286                     LOG.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes());
287                     LOG.info("Instance-identifier will be translated as NULL for data - {}",
288                             String.valueOf(identityValue.getValue()));
289                     return null;
290                 }
291                 final QName qName = node.getQName();
292                 PathArgument pathArgument = null;
293                 if (identityValue.getPredicates().isEmpty()) {
294                     pathArgument = new NodeIdentifier(qName);
295                 } else {
296                     if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry
297                         final Predicate leafListPredicate = identityValue.getPredicates().get(0);
298                         // FIXME: this needs to be a hard error
299                         if (!leafListPredicate.isLeafList()) {
300                             LOG.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\"");
301                             LOG.info("Instance-identifier will be translated as NULL for data - {}",
302                                     String.valueOf(identityValue.getValue()));
303                             return null;
304                         }
305                         pathArgument = new NodeWithValue<>(qName, leafListPredicate.getValue());
306                     } else if (node instanceof ListSchemaNode) { // predicates are keys of list
307                         final DataNodeContainer listNode = (DataNodeContainer) node;
308                         final Map<QName, Object> predicatesMap = new HashMap<>();
309                         for (final Predicate predicate : identityValue.getPredicates()) {
310                             validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint,
311                                     schemaContext);
312                             final DataSchemaNode listKey = findInstanceDataChildByNameAndNamespace(listNode,
313                                     predicate.getName().getValue(), validNamespace);
314                             predicatesMap.put(listKey.getQName(), predicate.getValue());
315                         }
316                         pathArgument = NodeIdentifierWithPredicates.of(qName, predicatesMap);
317                     } else {
318                         // FIXME: this needs to be a hard error
319                         LOG.info("Node {} is not List or Leaf-list.", node);
320                         LOG.info("Instance-identifier will be translated as NULL for data - {}",
321                                 String.valueOf(identityValue.getValue()));
322                         return null;
323                     }
324                 }
325                 result.add(pathArgument);
326                 if (i < identities.size() - 1) { // last element in instance-identifier can be other than
327                     // DataNodeContainer
328                     if (node instanceof DataNodeContainer) {
329                         parentContainer = (DataNodeContainer) node;
330                     } else {
331                         // FIXME: this needs to be a hard error
332                         LOG.info("Node {} isn't instance of DataNodeContainer", node);
333                         LOG.info("Instance-identifier will be translated as NULL for data - {}",
334                                 String.valueOf(identityValue.getValue()));
335                         return null;
336                     }
337                 }
338             }
339
340             return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
341         }
342
343         private static List<Predicate> keyValuesToPredicateList(final Set<Entry<QName, Object>> keyValues) {
344             final List<Predicate> result = new ArrayList<>();
345             for (final Entry<QName, Object> entry : keyValues) {
346                 final QName qualifiedName = entry.getKey();
347                 final Object value = entry.getValue();
348                 result.add(new Predicate(qNameToIdentityValue(qualifiedName), String.valueOf(value)));
349             }
350             return result;
351         }
352
353         private static IdentityValue qNameToIdentityValue(final QName qualifiedName) {
354             if (qualifiedName != null) {
355                 return new IdentityValue(qualifiedName.getNamespace().toString(), qualifiedName.getLocalName());
356             }
357             return null;
358         }
359     }
360
361     private static Module getModuleByNamespace(final String namespace, final DOMMountPoint mountPoint,
362             final SchemaContext schemaContext) {
363         final XMLNamespace validNamespace = resolveValidNamespace(namespace, mountPoint, schemaContext);
364         Module module = null;
365         if (mountPoint != null) {
366             module = modelContext(mountPoint).findModules(validNamespace).iterator().next();
367         } else {
368             module = schemaContext.findModules(validNamespace).iterator().next();
369         }
370         if (module == null) {
371             LOG.info("Module for namespace {} was not found.", validNamespace);
372             return null;
373         }
374         return module;
375     }
376
377     private static XMLNamespace resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint,
378             final SchemaContext schemaContext) {
379         XMLNamespace validNamespace;
380         if (mountPoint != null) {
381             validNamespace = findFirstModuleByName(modelContext(mountPoint), namespace);
382         } else {
383             validNamespace = findFirstModuleByName(schemaContext, namespace);
384         }
385         if (validNamespace == null) {
386             validNamespace = XMLNamespace.of(namespace);
387         }
388
389         return validNamespace;
390     }
391
392     private static XMLNamespace findFirstModuleByName(final SchemaContext schemaContext, final String name) {
393         for (final Module module : schemaContext.getModules()) {
394             if (module.getName().equals(name)) {
395                 return module.getNamespace();
396             }
397         }
398         return null;
399     }
400
401     private static DataSchemaNode findInstanceDataChildByNameAndNamespace(final DataNodeContainer container,
402             final String name, final XMLNamespace namespace) {
403         requireNonNull(namespace);
404
405         final Iterable<DataSchemaNode> result = Iterables.filter(findInstanceDataChildrenByName(container, name),
406             node -> namespace.equals(node.getQName().getNamespace()));
407         return Iterables.getFirst(result, null);
408     }
409
410     private static List<DataSchemaNode> findInstanceDataChildrenByName(final DataNodeContainer container,
411             final String name) {
412         final List<DataSchemaNode> instantiatedDataNodeContainers = new ArrayList<>();
413         collectInstanceDataNodeContainers(instantiatedDataNodeContainers, requireNonNull(container),
414             requireNonNull(name));
415         return instantiatedDataNodeContainers;
416     }
417
418     private static void collectInstanceDataNodeContainers(final List<DataSchemaNode> potentialSchemaNodes,
419             final DataNodeContainer container, final String name) {
420
421         final Iterable<? extends DataSchemaNode> nodes =
422                 Iterables.filter(container.getChildNodes(), node -> name.equals(node.getQName().getLocalName()));
423
424         // Can't combine this loop with the filter above because the filter is
425         // lazily-applied by Iterables.filter.
426         for (final DataSchemaNode potentialNode : nodes) {
427             if (isInstantiatedDataSchema(potentialNode)) {
428                 potentialSchemaNodes.add(potentialNode);
429             }
430         }
431
432         final Iterable<ChoiceSchemaNode> choiceNodes =
433                 Iterables.filter(container.getChildNodes(), ChoiceSchemaNode.class);
434         final Iterable<Collection<? extends CaseSchemaNode>> map = Iterables.transform(choiceNodes,
435             ChoiceSchemaNode::getCases);
436         for (final CaseSchemaNode caze : Iterables.concat(map)) {
437             collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name);
438         }
439     }
440
441     private static boolean isInstantiatedDataSchema(final DataSchemaNode node) {
442         return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode
443                 || node instanceof ContainerSchemaNode || node instanceof ListSchemaNode
444                 || node instanceof AnyxmlSchemaNode;
445     }
446
447     private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {
448         return mountPoint.getService(DOMSchemaService.class)
449             .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
450             .orElse(null);
451     }
452 }