Java tutorial
/* * Copyright 2017-present Open Networking Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.d.config; import com.google.common.annotations.Beta; import org.apache.commons.text.StringEscapeUtils; import org.onosproject.yang.model.DataNode; import org.onosproject.yang.model.KeyLeaf; import org.onosproject.yang.model.LeafListKey; import org.onosproject.yang.model.LeafListKey.LeafListKeyBuilder; import org.onosproject.yang.model.ListKey.ListKeyBuilder; import org.onosproject.yang.model.ListKey; import org.onosproject.yang.model.NodeKey; import org.onosproject.yang.model.ResourceId; import org.onosproject.yang.model.ResourceId.Builder; import org.onosproject.yang.model.SchemaId; import org.slf4j.Logger; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static org.slf4j.LoggerFactory.getLogger; /** * Utility related to ResourceId. */ @Beta public abstract class ResourceIds { private static final Logger log = getLogger(ResourceIds.class); /** * Root resource Id used by Yang Runtime. * (name: {@code "/"}, nameSpace: {@code null}) */ public static final ResourceId YRS_ROOT = ResourceId.builder().addBranchPointSchema("/", null).build(); /** * Absolute ResourceId pointing at root node. * (name: {@link DeviceResourceIds#ROOT_NAME}, * nameSpace: {@link DeviceResourceIds#DCS_NAMESPACE}) */ public static final ResourceId ROOT_ID = ResourceId.builder() .addBranchPointSchema(DeviceResourceIds.ROOT_NAME, DeviceResourceIds.DCS_NAMESPACE).build(); /** * Builds the ResourceId of specified {@code node}. * * @param parent ResourceId of {@code node} parent * @param node to create ResourceId. * @return ResourceId of {@code node} */ public static ResourceId resourceId(ResourceId parent, DataNode node) { final ResourceId.Builder builder; if (parent == null) { builder = ResourceId.builder(); } else { try { builder = parent.copyBuilder(); } catch (CloneNotSupportedException e) { throw new IllegalArgumentException(e); } } SchemaId sid = node.key().schemaId(); switch (node.type()) { case MULTI_INSTANCE_LEAF_VALUE_NODE: builder.addLeafListBranchPoint(sid.name(), sid.namespace(), ((LeafListKey) node.key()).asString()); break; case MULTI_INSTANCE_NODE: builder.addBranchPointSchema(sid.name(), sid.namespace()); for (KeyLeaf keyLeaf : ((ListKey) node.key()).keyLeafs()) { builder.addKeyLeaf(keyLeaf.leafSchema().name(), keyLeaf.leafSchema().namespace(), keyLeaf.leafValAsString()); } break; case SINGLE_INSTANCE_LEAF_VALUE_NODE: case SINGLE_INSTANCE_NODE: builder.addBranchPointSchema(sid.name(), sid.namespace()); break; default: throw new IllegalArgumentException("Unknown type " + node); } return builder.build(); } /** * Concats {@code path} after {@code prefix}. * * @param prefix path * @param path to append after {@code path} * @return concatenated ResouceId */ public static ResourceId concat(ResourceId prefix, ResourceId path) { checkArgument(!path.nodeKeys().contains(DeviceResourceIds.ROOT_NODE), "%s was already absolute path", path); try { return prefix.copyBuilder().append(path).build(); } catch (CloneNotSupportedException e) { log.error("Could not copy {}", path, e); throw new IllegalArgumentException("Could not copy " + path, e); } } /** * Returns {@code child} as relative ResourceId against {@code base}. * * @param base ResourceId * @param child ResourceId to relativize * @return relative ResourceId */ public static ResourceId relativize(ResourceId base, ResourceId child) { checkArgument(child.nodeKeys().size() >= base.nodeKeys().size(), "%s path must be deeper than base prefix %s", child, base); @SuppressWarnings("rawtypes") Iterator<NodeKey> bIt = base.nodeKeys().iterator(); @SuppressWarnings("rawtypes") Iterator<NodeKey> cIt = child.nodeKeys().iterator(); while (bIt.hasNext()) { NodeKey<?> b = bIt.next(); NodeKey<?> c = cIt.next(); checkArgument(Objects.equals(b, c), "%s is not a prefix of %s.\n" + "b:%s != c:%s", base, child, b, c); } return ResourceId.builder() .append(child.nodeKeys().subList(base.nodeKeys().size(), child.nodeKeys().size())).build(); } /** * Removes the root node from {@code path}. * * @param path given resource ID * @return resource ID without root node */ public static ResourceId removeRootNode(ResourceId path) { if (!startsWithRootNode(path)) { return path; } return ResourceId.builder().append(path.nodeKeys().subList(1, path.nodeKeys().size())).build(); } /** * Returns the resource ID of the parent data node pointed by {@code path}. * * @param path resource ID of the given data node * @return resource ID of the parent data node */ public static ResourceId parentOf(ResourceId path) { try { return path.copyBuilder().removeLastKey().build(); } catch (CloneNotSupportedException e) { log.error("Could not copy {}", path, e); throw new IllegalArgumentException("Could not copy " + path, e); } } /** * Tests if {@code child} starts with {@code prefix}. * * @param prefix expected * @param child to test * @return true if {@code child} starts with {@code prefix} */ public static boolean isPrefix(ResourceId prefix, ResourceId child) { return child.nodeKeys().size() >= prefix.nodeKeys().size() && prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size())); } /** * Tests if {@code path} starts with {@link DeviceResourceIds#ROOT_NAME}. * * @param path to test * @return true if {@code path} starts with {@link DeviceResourceIds#ROOT_NAME} */ public static boolean startsWithRootNode(ResourceId path) { return !path.nodeKeys().isEmpty() && DeviceResourceIds.ROOT_NAME.equals(path.nodeKeys().get(0).schemaId().name()); } /** * Converts node-identifier element to a NodeKey. * * @param id to parse (node-identifier fragment between '/') * @return NodeKey (warning: returned namespace can be null, which should be interpreted as * same as parent) */ private static NodeKey toNodeKey(String id) { Pattern nodeId = Pattern.compile( "^((?<prefix>[a-zA-Z_](?:[a-zA-Z0-9_.\\-]*)):)?" + "(?<identifier>[a-zA-Z_](?:[a-zA-Z0-9_.-]*))"); Matcher nidMatcher = nodeId.matcher(id); if (!nidMatcher.find()) { throw new IllegalArgumentException("node identifier not found in " + id); } String prefix = nidMatcher.group("prefix"); String identifier = nidMatcher.group("identifier"); // key and val pattern is a bit loosened from RFC for simplicity Pattern preds = Pattern.compile("\\[\\s*(?<key>[^=\\s]+)\\s*=\\s*\\\"(?<val>[^\\]]+)\\\"\\s*\\]"); Matcher predMatcher = preds.matcher(id); predMatcher.region(nidMatcher.end(), id.length()); LeafListKeyBuilder llkb = null; ListKeyBuilder llb = null; while (predMatcher.find()) { String key = predMatcher.group("key"); String val = predMatcher.group("val"); if (key.equals(".")) { // LeafList if (llkb == null) { llkb = new LeafListKeyBuilder(); } llkb.schemaId(identifier, prefix).value(val); } else { // ListKey if (llb == null) { llb = new ListKeyBuilder(); } llb.schemaId(identifier, prefix); Matcher m = nodeId.matcher(key); m.matches(); llb.addKeyLeaf(m.group("identifier"), m.group("prefix"), val); } } if (llkb != null) { return llkb.build(); } else if (llb != null) { return llb.build(); } else { return NodeKey.builder().schemaId(identifier, prefix).build(); } } /** * Add {@link #YRS_ROOT} prefix if not already. * * @param rid resource id * @return ResourceId starting from {@link #YRS_ROOT} */ public static ResourceId prefixYrsRoot(ResourceId rid) { if (rid == null) { return YRS_ROOT; } if (isPrefix(YRS_ROOT, rid)) { return rid; } if (isPrefix(ROOT_ID, rid)) { return concat(YRS_ROOT, relativize(ROOT_ID, rid)); } return concat(YRS_ROOT, rid); } /** * Add {@link #ROOT_ID} prefix if not already. * * @param rid resource id * @return ResourceId starting from {@link #ROOT_ID} */ public static ResourceId prefixDcsRoot(ResourceId rid) { if (rid == null) { return ROOT_ID; } if (isPrefix(ROOT_ID, rid)) { return rid; } // test and replace YangRuntime root? if (isPrefix(YRS_ROOT, rid)) { return concat(ROOT_ID, relativize(YRS_ROOT, rid)); } return concat(ROOT_ID, rid); } /** * Converts instance-identifier String into a ResourceId. * * @param input instance-identifier * @return Corresponding ResourceId relative to root or null if {@code iid} is '/' * Returned ResourceId will not have root NodeKey in it's path. * consider using {@link #prefixDcsRoot(ResourceId)}, * {@link #prefixYrsRoot(ResourceId)} to add them. */ public static ResourceId fromInstanceIdentifier(String input) { String[] nodes = input.split("/"); List<NodeKey> nodeKeys = Arrays.stream(nodes).filter(s -> !s.isEmpty()).map(ResourceIds::toNodeKey) .collect(Collectors.toList()); if (nodeKeys.isEmpty()) { return null; } Builder builder = ResourceId.builder(); // fill-in null (=inherit from parent) nameSpace String lastNamespace = null; for (NodeKey nodeKey : nodeKeys) { if (nodeKey.schemaId().namespace() != null) { lastNamespace = nodeKey.schemaId().namespace(); } if (nodeKey instanceof LeafListKey) { builder.addLeafListBranchPoint(nodeKey.schemaId().name(), firstNonNull(nodeKey.schemaId().namespace(), lastNamespace), ((LeafListKey) nodeKey).value()); } else if (nodeKey instanceof ListKey) { builder.addBranchPointSchema(nodeKey.schemaId().name(), lastNamespace); for (KeyLeaf kl : ((ListKey) nodeKey).keyLeafs()) { builder.addKeyLeaf(kl.leafSchema().name(), firstNonNull(kl.leafSchema().namespace(), lastNamespace), kl.leafValue()); } } else { builder.addBranchPointSchema(nodeKey.schemaId().name(), lastNamespace); } } return builder.build(); } /** * Converts ResourceId to instance-identifier. * * @param rid to convert * @return instance-identifier * * @see <a href="https://tools.ietf.org/html/rfc7951#section-6.11">RFC 7951</a> * @see <a href="https://tools.ietf.org/html/rfc7950#section-14">RFC 7950 for ABNF</a> */ public static String toInstanceIdentifier(ResourceId rid) { StringBuilder s = new StringBuilder(); String lastNamespace = null; for (NodeKey nk : rid.nodeKeys()) { if (nk.schemaId().name().equals("/")) { // special handling for root nodeKey: skip it // YANG runtime root: null:/ // DCS root: org.onosproject.dcs:/ continue; } s.append('/'); if (!Objects.equals(lastNamespace, nk.schemaId().namespace())) { s.append(nk.schemaId().namespace()); s.append(':'); lastNamespace = nk.schemaId().namespace(); } s.append(nk.schemaId().name()); if (nk instanceof LeafListKey) { LeafListKey llk = (LeafListKey) nk; s.append('['); s.append('.'); s.append('='); s.append('"').append(StringEscapeUtils.escapeJson(llk.asString())).append('"'); s.append(']'); } else if (nk instanceof ListKey) { ListKey lk = (ListKey) nk; for (KeyLeaf kl : lk.keyLeafs()) { s.append('['); if (!Objects.equals(kl.leafSchema().namespace(), lastNamespace)) { s.append(kl.leafSchema().namespace()); s.append(':'); } s.append(kl.leafSchema().name()); s.append('='); s.append('"').append(StringEscapeUtils.escapeJson(kl.leafValAsString())).append('"'); s.append(']'); } } else { // normal NodeKey // nothing to do } } if (s.length() == 0) { return "/"; } return s.toString(); } }