summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
blob: 5d7a840e57c187b5d85e77fe6ec1504b91d11447 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.netconf.sal.restconf.impl;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
import org.opendaylight.restconf.common.util.IdentityValuesDTO;
import org.opendaylight.restconf.common.util.IdentityValuesDTO.IdentityValue;
import org.opendaylight.restconf.common.util.IdentityValuesDTO.Predicate;
import org.opendaylight.restconf.common.util.RestUtil;
import org.opendaylight.yangtools.concepts.IllegalArgumentCodec;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec;
import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec;
import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class RestCodec {

    private static final Logger LOG = LoggerFactory.getLogger(RestCodec.class);

    private RestCodec() {
    }

    // FIXME: IllegalArgumentCodec is not quite accurate
    public static IllegalArgumentCodec<Object, Object> from(final TypeDefinition<?> typeDefinition,
            final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
        return new ObjectCodec(typeDefinition, mountPoint, controllerContext);
    }

    @SuppressWarnings("rawtypes")
    public static final class ObjectCodec implements IllegalArgumentCodec<Object, Object> {

        private static final Logger LOG = LoggerFactory.getLogger(ObjectCodec.class);

        public static final IllegalArgumentCodec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl();

        private final ControllerContext controllerContext;
        private final IllegalArgumentCodec instanceIdentifier;
        private final IllegalArgumentCodec identityrefCodec;

        private final TypeDefinition<?> type;

        private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint,
                final ControllerContext controllerContext) {
            this.controllerContext = controllerContext;
            type = RestUtil.resolveBaseTypeFrom(typeDefinition);
            if (type instanceof IdentityrefTypeDefinition) {
                identityrefCodec = new IdentityrefCodecImpl(mountPoint, controllerContext);
            } else {
                identityrefCodec = null;
            }
            if (type instanceof InstanceIdentifierTypeDefinition) {
                instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint, controllerContext);
            } else {
                instanceIdentifier = null;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Legacy code")
        public Object deserialize(final Object input) {
            try {
                if (type instanceof IdentityrefTypeDefinition) {
                    if (input instanceof IdentityValuesDTO) {
                        return identityrefCodec.deserialize(input);
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                            "Value is not instance of IdentityrefTypeDefinition but is {}. "
                                    + "Therefore NULL is used as translation of  - {}",
                            input == null ? "null" : input.getClass(), String.valueOf(input));
                    }
                    // FIXME: this should be a hard error
                    return null;
                } else if (type instanceof InstanceIdentifierTypeDefinition) {
                    if (input instanceof IdentityValuesDTO) {
                        return instanceIdentifier.deserialize(input);
                    } else {
                        final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
                                controllerContext.getGlobalSchema());
                        return codec.deserialize((String) input);
                    }
                } else {
                    final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
                            TypeDefinitionAwareCodec.from(type);
                    if (typeAwarecodec != null) {
                        if (input instanceof IdentityValuesDTO) {
                            return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
                        }
                        return typeAwarecodec.deserialize(String.valueOf(input));
                    } else {
                        LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
                        // FIXME: this should be a hard error
                        return null;
                    }
                }
            } catch (final ClassCastException e) { // TODO remove this catch when everyone use codecs
                LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
                // FIXME: this should be a hard error
                return null;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "legacy code")
        public Object serialize(final Object input) {
            try {
                if (type instanceof IdentityrefTypeDefinition) {
                    return identityrefCodec.serialize(input);
                } else if (type instanceof LeafrefTypeDefinition) {
                    return LEAFREF_DEFAULT_CODEC.serialize(input);
                } else if (type instanceof InstanceIdentifierTypeDefinition) {
                    return instanceIdentifier.serialize(input);
                } else {
                    final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec =
                            TypeDefinitionAwareCodec.from(type);
                    if (typeAwarecodec != null) {
                        return typeAwarecodec.serialize(input);
                    } else {
                        LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName().getLocalName());
                        return null;
                    }
                }
            } catch (final ClassCastException e) {
                // FIXME: remove this catch when everyone use codecs
                LOG.error("ClassCastException was thrown when codec is invoked with parameter {}", input, e);
                // FIXME: this should be a hard error
                return input;
            }
        }

    }

    public static class IdentityrefCodecImpl implements IdentityrefCodec<IdentityValuesDTO> {
        private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class);

        private final DOMMountPoint mountPoint;
        private final ControllerContext controllerContext;

        public IdentityrefCodecImpl(final DOMMountPoint mountPoint, final ControllerContext controllerContext) {
            this.mountPoint = mountPoint;
            this.controllerContext = controllerContext;
        }

        @Override
        public IdentityValuesDTO serialize(final QName data) {
            return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), null, null);
        }

        @Override
        @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "See FIXME below")
        public QName deserialize(final IdentityValuesDTO data) {
            final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
                    controllerContext);
            if (module == null) {
                // FIXME: this should be a hard error
                LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
                LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
                return null;
            }

            return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue());
        }
    }

    public static class LeafrefCodecImpl implements LeafrefCodec<String> {

        @Override
        public String serialize(final Object data) {
            return String.valueOf(data);
        }

        @Override
        public Object deserialize(final String data) {
            return data;
        }

    }

    public static class InstanceIdentifierCodecImpl implements InstanceIdentifierCodec<IdentityValuesDTO> {
        private static final Logger LOG = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class);

        private final DOMMountPoint mountPoint;
        private final ControllerContext controllerContext;

        public InstanceIdentifierCodecImpl(final DOMMountPoint mountPoint,
                final ControllerContext controllerContext) {
            this.mountPoint = mountPoint;
            this.controllerContext = controllerContext;
        }

        @Override
        public IdentityValuesDTO serialize(final YangInstanceIdentifier data) {
            final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
            for (final PathArgument pathArgument : data.getPathArguments()) {
                final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
                if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
                    final List<Predicate> predicates =
                            keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument).entrySet());
                    identityValue.setPredicates(predicates);
                } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
                    final List<Predicate> predicates = new ArrayList<>();
                    final String value = String.valueOf(((NodeWithValue<?>) pathArgument).getValue());
                    predicates.add(new Predicate(null, value));
                    identityValue.setPredicates(predicates);
                }
                identityValuesDTO.add(identityValue);
            }
            return identityValuesDTO;
        }

        @SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", "NP_NONNULL_RETURN_VIOLATION" },
                justification = "Unrecognised NullableDecl")
        @Override
        public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
            final List<PathArgument> result = new ArrayList<>();
            final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint,
                    controllerContext);
            if (module == null) {
                LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
                        valueWithNamespace.getNamespace());
                LOG.info("Instance-identifier will be translated as NULL for data - {}",
                        String.valueOf(valueWithNamespace.getValue()));
                // FIXME: this should be a hard error
                return null;
            }

            DataNodeContainer parentContainer = module;
            final List<IdentityValue> identities = data.getValuesWithNamespaces();
            for (int i = 0; i < identities.size(); i++) {
                final IdentityValue identityValue = identities.get(i);
                XMLNamespace validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint,
                        controllerContext);
                final var found = ControllerContext.findInstanceDataChildByNameAndNamespace(
                    parentContainer, identityValue.getValue(), validNamespace);
                if (found == null) {
                    LOG.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes());
                    LOG.info("Instance-identifier will be translated as NULL for data - {}",
                            String.valueOf(identityValue.getValue()));
                    // FIXME: this should be a hard error
                    return null;
                }
                final DataSchemaNode node = found.child;
                final QName qName = node.getQName();
                PathArgument pathArgument = null;
                if (identityValue.getPredicates().isEmpty()) {
                    pathArgument = new NodeIdentifier(qName);
                } else {
                    if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry
                        final Predicate leafListPredicate = identityValue.getPredicates().get(0);
                        if (!leafListPredicate.isLeafList()) {
                            LOG.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\"");
                            LOG.info("Instance-identifier will be translated as NULL for data - {}",
                                    String.valueOf(identityValue.getValue()));
                            // FIXME: this should be a hard error
                            return null;
                        }
                        pathArgument = new NodeWithValue<>(qName, leafListPredicate.getValue());
                    } else if (node instanceof ListSchemaNode) { // predicates are keys of list
                        final DataNodeContainer listNode = (DataNodeContainer) node;
                        final Map<QName, Object> predicatesMap = new HashMap<>();
                        for (final Predicate predicate : identityValue.getPredicates()) {
                            validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint,
                                    controllerContext);
                            final var listKey = ControllerContext
                                    .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
                                            validNamespace);
                            predicatesMap.put(listKey.child.getQName(), predicate.getValue());
                        }
                        pathArgument = NodeIdentifierWithPredicates.of(qName, predicatesMap);
                    } else {
                        LOG.info("Node {} is not List or Leaf-list.", node);
                        LOG.info("Instance-identifier will be translated as NULL for data - {}",
                                String.valueOf(identityValue.getValue()));
                        // FIXME: this should be a hard error
                        return null;
                    }
                }
                result.add(pathArgument);
                if (i < identities.size() - 1) { // last element in instance-identifier can be other than
                    // DataNodeContainer
                    if (node instanceof DataNodeContainer) {
                        parentContainer = (DataNodeContainer) node;
                    } else {
                        LOG.info("Node {} isn't instance of DataNodeContainer", node);
                        LOG.info("Instance-identifier will be translated as NULL for data - {}",
                                String.valueOf(identityValue.getValue()));
                        // FIXME: this should be a hard error
                        return null;
                    }
                }
            }

            return result.isEmpty() ? null : YangInstanceIdentifier.create(result);
        }

        private static List<Predicate> keyValuesToPredicateList(final Set<Entry<QName, Object>> keyValues) {
            final List<Predicate> result = new ArrayList<>();
            for (final Entry<QName, Object> entry : keyValues) {
                final QName qualifiedName = entry.getKey();
                final Object value = entry.getValue();
                result.add(new Predicate(qNameToIdentityValue(qualifiedName), String.valueOf(value)));
            }
            return result;
        }

        private static IdentityValue qNameToIdentityValue(final QName qualifiedName) {
            if (qualifiedName != null) {
                return new IdentityValue(qualifiedName.getNamespace().toString(), qualifiedName.getLocalName());
            }
            return null;
        }
    }

    private static Module getModuleByNamespace(final String namespace, final DOMMountPoint mountPoint,
            final ControllerContext controllerContext) {
        final XMLNamespace validNamespace = resolveValidNamespace(namespace, mountPoint, controllerContext);

        Module module = null;
        if (mountPoint != null) {
            module = ControllerContext.findModuleByNamespace(mountPoint, validNamespace);
        } else {
            module = controllerContext.findModuleByNamespace(validNamespace);
        }
        if (module == null) {
            LOG.info("Module for namespace {} was not found.", validNamespace);
            return null;
        }
        return module;
    }

    private static XMLNamespace resolveValidNamespace(final String namespace, final DOMMountPoint mountPoint,
            final ControllerContext controllerContext) {
        XMLNamespace validNamespace;
        if (mountPoint != null) {
            validNamespace = ControllerContext.findNamespaceByModuleName(mountPoint, namespace);
        } else {
            validNamespace = controllerContext.findNamespaceByModuleName(namespace);
        }
        if (validNamespace == null) {
            validNamespace = XMLNamespace.of(namespace);
        }

        return validNamespace;
    }
}