Fixed wrong exception types
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierSerializer.java
1 /*
2  * Copyright (c) 2016 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.utils.parser;
9
10 import java.util.Iterator;
11 import java.util.Locale;
12 import java.util.Map.Entry;
13 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
14 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
15 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
16 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
17 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
18 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Serializer;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
26 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
27 import org.opendaylight.yangtools.yang.model.api.Module;
28 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
29
30 /**
31  * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
32  */
33 public final class YangInstanceIdentifierSerializer {
34
35     private YangInstanceIdentifierSerializer() {
36         throw new UnsupportedOperationException("Util class.");
37     }
38
39     /**
40      * Method to create String from {@link Iterable} of {@link PathArgument}
41      * which are parsing from data by {@link SchemaContext}.
42      *
43      * @param schemaContext
44      *             for validate of parsing path arguments
45      * @param data
46      *             path to data
47      * @return {@link String}
48      */
49     public static String create(final SchemaContext schemaContext, final YangInstanceIdentifier data) {
50         final DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
51         final MainVarsWrapper variables = new MainVarsWrapper(current);
52         final StringBuilder path = new StringBuilder();
53
54         QNameModule parentModule = null;
55         for (int i = 0; i < data.getPathArguments().size(); i++) {
56             // get module of the parent
57             if (!variables.getCurrent().isMixin()) {
58                 parentModule = variables.getCurrent().getDataSchemaNode().getQName().getModule();
59             }
60
61             final PathArgument arg = data.getPathArguments().get(i);
62             variables.setCurrent(variables.getCurrent().getChild(arg));
63             RestconfDocumentedException.throwIf(variables.getCurrent() == null, ErrorType.APPLICATION,
64                     ErrorTag.UNKNOWN_ELEMENT, "Invalid input '%s': schema for argument '%s' (after '%s') not found",
65                     data, arg, path);
66             if (variables.getCurrent().isMixin()) {
67                 continue;
68             }
69
70             // append namespace before every node which is defined in other module than its parent
71             // condition is satisfied also for the first path argument
72             if (!arg.getNodeType().getModule().equals(parentModule)) {
73                 // append slash if it is not the first path argument
74                 if (path.length() > 0) {
75                     path.append(RestconfConstants.SLASH);
76                 }
77
78                 path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
79                 path.append(ParserBuilderConstants.Deserializer.COLON);
80             } else {
81                 path.append(RestconfConstants.SLASH);
82             }
83
84             if (arg instanceof NodeIdentifierWithPredicates) {
85                 prepareNodeWithPredicates(path, arg);
86             } else if (arg instanceof NodeWithValue) {
87                 prepareNodeWithValue(path, arg);
88             } else {
89                 appendQName(path, arg.getNodeType());
90             }
91         }
92
93         return path.toString();
94     }
95
96     private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
97         path.append(arg.getNodeType().getLocalName());
98         path.append(ParserBuilderConstants.Deserializer.EQUAL);
99
100         String value = String.valueOf(((NodeWithValue<String>) arg).getValue());
101         if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
102             value = parsePercentEncodeChars(value);
103         }
104         path.append(value);
105     }
106
107     private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
108         path.append(arg.getNodeType().getLocalName());
109
110         final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).getKeyValues()
111                 .entrySet().iterator();
112
113         if (iterator.hasNext()) {
114             path.append(ParserBuilderConstants.Deserializer.EQUAL);
115         }
116
117         while (iterator.hasNext()) {
118             String valueOf = String.valueOf(iterator.next().getValue());
119             if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
120                 valueOf = parsePercentEncodeChars(valueOf);
121             }
122             path.append(valueOf);
123             if (iterator.hasNext()) {
124                 path.append(ParserBuilderConstants.Deserializer.COMMA);
125             }
126         }
127     }
128
129     /**
130      * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars.
131      *
132      * @param valueOf
133      *             string to encode
134      * @return encoded {@link String}
135      */
136     private static String parsePercentEncodeChars(final String valueOf) {
137         final StringBuilder sb = new StringBuilder();
138         int start = 0;
139         while (start < valueOf.length()) {
140             if (Serializer.PERCENT_ENCODE_CHARS.matches(valueOf.charAt(start))) {
141                 final String format = String.format("%x", (int) valueOf.charAt(start));
142                 final String upperCase = format.toUpperCase(Locale.ROOT);
143                 sb.append(ParserBuilderConstants.Deserializer.PERCENT_ENCODING + upperCase);
144             } else {
145                 sb.append(valueOf.charAt(start));
146             }
147             start++;
148         }
149         return sb.toString();
150     }
151
152     /**
153      * Add {@link QName} to the serialized string.
154      *
155      * @param path
156      *             {@link StringBuilder}
157      * @param qname
158      *             {@link QName} node
159      * @return {@link StringBuilder}
160      */
161     private static StringBuilder appendQName(final StringBuilder path, final QName qname) {
162         path.append(qname.getLocalName());
163         return path;
164     }
165
166     /**
167      * Create prefix of namespace from {@link QName}.
168      *
169      * @param qname
170      *             {@link QName}
171      * @return {@link String}
172      */
173     private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
174         final Module module = schemaContext.findModule(qname.getModule()).orElse(null);
175         return module.getName();
176     }
177
178     private static final class MainVarsWrapper {
179
180         private DataSchemaContextNode<?> current;
181
182         MainVarsWrapper(final DataSchemaContextNode<?> current) {
183             this.setCurrent(current);
184         }
185
186         public DataSchemaContextNode<?> getCurrent() {
187             return this.current;
188         }
189
190         public void setCurrent(final DataSchemaContextNode<?> current) {
191             this.current = current;
192         }
193
194     }
195 }