blob: bff42b56e71134323f53e144717cfd91e2d257a8 [file] [log] [blame]
Yuta HIGUCHI24057822017-08-02 15:03:51 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.d.config;
17
Henry Yu830b5dc2017-11-16 10:44:45 -050018import com.google.common.annotations.Beta;
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080019import org.apache.commons.text.StringEscapeUtils;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070020import org.onosproject.yang.model.DataNode;
21import org.onosproject.yang.model.KeyLeaf;
22import org.onosproject.yang.model.LeafListKey;
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080023import org.onosproject.yang.model.LeafListKey.LeafListKeyBuilder;
24import org.onosproject.yang.model.ListKey.ListKeyBuilder;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070025import org.onosproject.yang.model.ListKey;
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -070026import org.onosproject.yang.model.NodeKey;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070027import org.onosproject.yang.model.ResourceId;
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080028import org.onosproject.yang.model.ResourceId.Builder;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070029import org.onosproject.yang.model.SchemaId;
30import org.slf4j.Logger;
31
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080032import java.util.Arrays;
Henry Yu830b5dc2017-11-16 10:44:45 -050033import java.util.Iterator;
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080034import java.util.List;
Henry Yu830b5dc2017-11-16 10:44:45 -050035import java.util.Objects;
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080036import java.util.regex.Matcher;
37import java.util.regex.Pattern;
38import java.util.stream.Collectors;
Henry Yu830b5dc2017-11-16 10:44:45 -050039
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080040import static com.google.common.base.MoreObjects.firstNonNull;
Henry Yu830b5dc2017-11-16 10:44:45 -050041import static com.google.common.base.Preconditions.checkArgument;
42import static org.slf4j.LoggerFactory.getLogger;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070043
44/**
45 * Utility related to ResourceId.
46 */
47@Beta
48public abstract class ResourceIds {
49
50 private static final Logger log = getLogger(ResourceIds.class);
51
52 /**
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080053 * Root resource Id used by Yang Runtime.
54 * (name: {@code "/"}, nameSpace: {@code null})
55 */
56 public static final ResourceId YRS_ROOT =
57 ResourceId.builder().addBranchPointSchema("/", null).build();
58 /**
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -070059 * Absolute ResourceId pointing at root node.
Yuta HIGUCHI825401e2018-02-27 13:23:25 -080060 * (name: {@link DeviceResourceIds#ROOT_NAME},
61 * nameSpace: {@link DeviceResourceIds#DCS_NAMESPACE})
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -070062 */
63 public static final ResourceId ROOT_ID = ResourceId.builder()
64 .addBranchPointSchema(DeviceResourceIds.ROOT_NAME,
65 DeviceResourceIds.DCS_NAMESPACE)
66 .build();
67
68 /**
Yuta HIGUCHI24057822017-08-02 15:03:51 -070069 * Builds the ResourceId of specified {@code node}.
70 *
71 * @param parent ResourceId of {@code node} parent
Henry Yu830b5dc2017-11-16 10:44:45 -050072 * @param node to create ResourceId.
Yuta HIGUCHI24057822017-08-02 15:03:51 -070073 * @return ResourceId of {@code node}
74 */
75 public static ResourceId resourceId(ResourceId parent, DataNode node) {
76 final ResourceId.Builder builder;
77 if (parent == null) {
78 builder = ResourceId.builder();
79 } else {
80 try {
81 builder = parent.copyBuilder();
82 } catch (CloneNotSupportedException e) {
83 throw new IllegalArgumentException(e);
84 }
85 }
86
87 SchemaId sid = node.key().schemaId();
88 switch (node.type()) {
Henry Yu830b5dc2017-11-16 10:44:45 -050089 case MULTI_INSTANCE_LEAF_VALUE_NODE:
90 builder.addLeafListBranchPoint(sid.name(), sid.namespace(),
91 ((LeafListKey) node.key()).asString());
92 break;
Yuta HIGUCHI24057822017-08-02 15:03:51 -070093
Henry Yu830b5dc2017-11-16 10:44:45 -050094 case MULTI_INSTANCE_NODE:
95 builder.addBranchPointSchema(sid.name(), sid.namespace());
96 for (KeyLeaf keyLeaf : ((ListKey) node.key()).keyLeafs()) {
97 builder.addKeyLeaf(keyLeaf.leafSchema().name(),
98 keyLeaf.leafSchema().namespace(),
99 keyLeaf.leafValAsString());
100 }
101 break;
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700102
Henry Yu830b5dc2017-11-16 10:44:45 -0500103 case SINGLE_INSTANCE_LEAF_VALUE_NODE:
104 case SINGLE_INSTANCE_NODE:
105 builder.addBranchPointSchema(sid.name(), sid.namespace());
106 break;
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700107
Henry Yu830b5dc2017-11-16 10:44:45 -0500108 default:
109 throw new IllegalArgumentException("Unknown type " + node);
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700110
111 }
112
113 return builder.build();
114 }
115
116 /**
117 * Concats {@code path} after {@code prefix}.
118 *
119 * @param prefix path
Henry Yu830b5dc2017-11-16 10:44:45 -0500120 * @param path to append after {@code path}
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700121 * @return concatenated ResouceId
122 */
123 public static ResourceId concat(ResourceId prefix, ResourceId path) {
124 checkArgument(!path.nodeKeys().contains(DeviceResourceIds.ROOT_NODE),
125 "%s was already absolute path", path);
126 try {
127 return prefix.copyBuilder().append(path).build();
128 } catch (CloneNotSupportedException e) {
129 log.error("Could not copy {}", path, e);
130 throw new IllegalArgumentException("Could not copy " + path, e);
131 }
132 }
133
134
135 /**
Yuta HIGUCHI4070c042017-08-28 13:47:20 -0700136 * Returns {@code child} as relative ResourceId against {@code base}.
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700137 *
Henry Yu830b5dc2017-11-16 10:44:45 -0500138 * @param base ResourceId
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700139 * @param child ResourceId to relativize
140 * @return relative ResourceId
141 */
142 public static ResourceId relativize(ResourceId base, ResourceId child) {
143 checkArgument(child.nodeKeys().size() >= base.nodeKeys().size(),
144 "%s path must be deeper than base prefix %s", child, base);
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -0700145 @SuppressWarnings("rawtypes")
146 Iterator<NodeKey> bIt = base.nodeKeys().iterator();
147 @SuppressWarnings("rawtypes")
148 Iterator<NodeKey> cIt = child.nodeKeys().iterator();
149 while (bIt.hasNext()) {
150 NodeKey<?> b = bIt.next();
151 NodeKey<?> c = cIt.next();
152
153 checkArgument(Objects.equals(b, c),
154 "%s is not a prefix of %s.\n" +
Henry Yu830b5dc2017-11-16 10:44:45 -0500155 "b:%s != c:%s",
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -0700156 base, child,
157 b, c);
158 }
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700159
Yuta HIGUCHI4070c042017-08-28 13:47:20 -0700160 return ResourceId.builder().append(child.nodeKeys().subList(base.nodeKeys().size(),
161 child.nodeKeys().size())).build();
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700162 }
163
164 /**
Henry Yu830b5dc2017-11-16 10:44:45 -0500165 * Removes the root node from {@code path}.
166 *
167 * @param path given resource ID
168 * @return resource ID without root node
169 */
170 public static ResourceId removeRootNode(ResourceId path) {
171 if (!startsWithRootNode(path)) {
172 return path;
173 }
174
175 return ResourceId.builder().append(path.nodeKeys().subList(1,
176 path.nodeKeys().size())).build();
177 }
178
179 /**
180 * Returns the resource ID of the parent data node pointed by {@code path}.
181 *
182 * @param path resource ID of the given data node
183 * @return resource ID of the parent data node
184 */
185 public static ResourceId parentOf(ResourceId path) {
186 try {
187 return path.copyBuilder().removeLastKey().build();
188 } catch (CloneNotSupportedException e) {
189 log.error("Could not copy {}", path, e);
190 throw new IllegalArgumentException("Could not copy " + path, e);
191 }
192 }
193
194 /**
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700195 * Tests if {@code child} starts with {@code prefix}.
196 *
197 * @param prefix expected
Henry Yu830b5dc2017-11-16 10:44:45 -0500198 * @param child to test
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700199 * @return true if {@code child} starts with {@code prefix}
200 */
201 public static boolean isPrefix(ResourceId prefix, ResourceId child) {
202
203 return child.nodeKeys().size() >= prefix.nodeKeys().size() &&
Henry Yu830b5dc2017-11-16 10:44:45 -0500204 prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size()));
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700205 }
206
Yuta HIGUCHI825401e2018-02-27 13:23:25 -0800207 /**
208 * Tests if {@code path} starts with {@link DeviceResourceIds#ROOT_NAME}.
209 *
210 * @param path to test
211 * @return true if {@code path} starts with {@link DeviceResourceIds#ROOT_NAME}
212 */
Yuta HIGUCHIe057dee2017-09-15 13:56:10 -0700213 public static boolean startsWithRootNode(ResourceId path) {
214 return !path.nodeKeys().isEmpty() &&
215 DeviceResourceIds.ROOT_NAME.equals(path.nodeKeys().get(0).schemaId().name());
216 }
217
Yuta HIGUCHI825401e2018-02-27 13:23:25 -0800218
219 /**
220 * Converts node-identifier element to a NodeKey.
221 *
222 * @param id to parse (node-identifier fragment between '/')
223 * @return NodeKey (warning: returned namespace can be null, which should be interpreted as
224 * same as parent)
225 */
226 private static NodeKey toNodeKey(String id) {
227 Pattern nodeId = Pattern.compile("^((?<prefix>[a-zA-Z_](?:[a-zA-Z0-9_.\\-]*)):)?"
228 + "(?<identifier>[a-zA-Z_](?:[a-zA-Z0-9_.-]*))");
229
230 Matcher nidMatcher = nodeId.matcher(id);
231 if (!nidMatcher.find()) {
232 throw new IllegalArgumentException("node identifier not found in " + id);
233 }
234
235 String prefix = nidMatcher.group("prefix");
236 String identifier = nidMatcher.group("identifier");
237
238 // key and val pattern is a bit loosened from RFC for simplicity
239 Pattern preds = Pattern.compile("\\[\\s*(?<key>[^=\\s]+)\\s*=\\s*\\\"(?<val>[^\\]]+)\\\"\\s*\\]");
240 Matcher predMatcher = preds.matcher(id);
241 predMatcher.region(nidMatcher.end(), id.length());
242 LeafListKeyBuilder llkb = null;
243 ListKeyBuilder llb = null;
244 while (predMatcher.find()) {
245 String key = predMatcher.group("key");
246 String val = predMatcher.group("val");
247 if (key.equals(".")) {
248 // LeafList
249 if (llkb == null) {
250 llkb = new LeafListKeyBuilder();
251 }
252 llkb.schemaId(identifier, prefix)
253 .value(val);
254 } else {
255 // ListKey
256 if (llb == null) {
257 llb = new ListKeyBuilder();
258 }
259 llb.schemaId(identifier, prefix);
260 Matcher m = nodeId.matcher(key);
261 m.matches();
262 llb.addKeyLeaf(m.group("identifier"), m.group("prefix"), val);
263 }
264 }
265 if (llkb != null) {
266 return llkb.build();
267 } else if (llb != null) {
268 return llb.build();
269 } else {
270 return NodeKey.builder().schemaId(identifier, prefix).build();
271 }
272 }
273
274
275 /**
276 * Add {@link #YRS_ROOT} prefix if not already.
277 *
278 * @param rid resource id
279 * @return ResourceId starting from {@link #YRS_ROOT}
280 */
281 public static ResourceId prefixYrsRoot(ResourceId rid) {
282 if (rid == null) {
283 return YRS_ROOT;
284 }
285
286 if (isPrefix(YRS_ROOT, rid)) {
287 return rid;
288 }
289
290 if (isPrefix(ROOT_ID, rid)) {
291 return concat(YRS_ROOT, relativize(ROOT_ID, rid));
292 }
293
294 return concat(YRS_ROOT, rid);
295 }
296
297 /**
298 * Add {@link #ROOT_ID} prefix if not already.
299 *
300 * @param rid resource id
301 * @return ResourceId starting from {@link #ROOT_ID}
302 */
303 public static ResourceId prefixDcsRoot(ResourceId rid) {
304 if (rid == null) {
305 return ROOT_ID;
306 }
307
308 if (isPrefix(ROOT_ID, rid)) {
309 return rid;
310 }
311
312 // test and replace YangRuntime root?
313 if (isPrefix(YRS_ROOT, rid)) {
314 return concat(ROOT_ID, relativize(YRS_ROOT, rid));
315 }
316
317 return concat(ROOT_ID, rid);
318 }
319
320
321 /**
322 * Converts instance-identifier String into a ResourceId.
323 *
324 * @param input instance-identifier
325 * @return Corresponding ResourceId relative to root or null if {@code iid} is '/'
326 * Returned ResourceId will not have root NodeKey in it's path.
327 * consider using {@link #prefixDcsRoot(ResourceId)},
328 * {@link #prefixYrsRoot(ResourceId)} to add them.
329 */
330 public static ResourceId fromInstanceIdentifier(String input) {
331
332 String[] nodes = input.split("/");
333 List<NodeKey> nodeKeys = Arrays.stream(nodes)
334 .filter(s -> !s.isEmpty())
335 .map(ResourceIds::toNodeKey)
336 .collect(Collectors.toList());
337
338 if (nodeKeys.isEmpty()) {
339 return null;
340 }
341
342 Builder builder = ResourceId.builder();
343
344 // fill-in null (=inherit from parent) nameSpace
345 String lastNamespace = null;
346 for (NodeKey nodeKey : nodeKeys) {
347 if (nodeKey.schemaId().namespace() != null) {
348 lastNamespace = nodeKey.schemaId().namespace();
349 }
350 if (nodeKey instanceof LeafListKey) {
351 builder.addLeafListBranchPoint(nodeKey.schemaId().name(),
352 firstNonNull(nodeKey.schemaId().namespace(), lastNamespace),
353 ((LeafListKey) nodeKey).value());
354
355 } else if (nodeKey instanceof ListKey) {
356 builder.addBranchPointSchema(nodeKey.schemaId().name(), lastNamespace);
357 for (KeyLeaf kl : ((ListKey) nodeKey).keyLeafs()) {
358 builder.addKeyLeaf(kl.leafSchema().name(),
359 firstNonNull(kl.leafSchema().namespace(), lastNamespace),
360 kl.leafValue());
361 }
362 } else {
363 builder.addBranchPointSchema(nodeKey.schemaId().name(), lastNamespace);
364 }
365 }
366 return builder.build();
367 }
368
369
370 /**
371 * Converts ResourceId to instance-identifier.
372 *
373 * @param rid to convert
374 * @return instance-identifier
375 *
376 * @see <a href="https://tools.ietf.org/html/rfc7951#section-6.11">RFC 7951</a>
377 * @see <a href="https://tools.ietf.org/html/rfc7950#section-14">RFC 7950 for ABNF</a>
378 */
379 public static String toInstanceIdentifier(ResourceId rid) {
380 StringBuilder s = new StringBuilder();
381
382 String lastNamespace = null;
383 for (NodeKey nk : rid.nodeKeys()) {
384 if (nk.schemaId().name().equals("/")) {
385 // special handling for root nodeKey: skip it
386 // YANG runtime root: null:/
387 // DCS root: org.onosproject.dcs:/
388 continue;
389 }
390
391 s.append('/');
392
393 if (!Objects.equals(lastNamespace, nk.schemaId().namespace())) {
394 s.append(nk.schemaId().namespace());
395 s.append(':');
396 lastNamespace = nk.schemaId().namespace();
397 }
398 s.append(nk.schemaId().name());
399
400 if (nk instanceof LeafListKey) {
401 LeafListKey llk = (LeafListKey) nk;
402 s.append('[');
403 s.append('.');
404
405 s.append('=');
406
407 s.append('"')
408 .append(StringEscapeUtils.escapeJson(llk.asString()))
409 .append('"');
410 s.append(']');
411
412 } else if (nk instanceof ListKey) {
413 ListKey lk = (ListKey) nk;
414
415 for (KeyLeaf kl : lk.keyLeafs()) {
416 s.append('[');
417
418 if (!Objects.equals(kl.leafSchema().namespace(), lastNamespace)) {
419 s.append(kl.leafSchema().namespace());
420 s.append(':');
421 }
422 s.append(kl.leafSchema().name());
423
424 s.append('=');
425
426 s.append('"')
427 .append(StringEscapeUtils.escapeJson(kl.leafValAsString()))
428 .append('"');
429 s.append(']');
430 }
431 } else {
432 // normal NodeKey
433 // nothing to do
434 }
435 }
436 if (s.length() == 0) {
437 return "/";
438 }
439 return s.toString();
440 }
441
Yuta HIGUCHI24057822017-08-02 15:03:51 -0700442}