ResourceId to instance-identifier string converter methods
part of ONOS-7503
Change-Id: I5c0b0c0c38f51ea1a94208c0b7cb9d4be1db060f
diff --git a/apps/config/BUCK b/apps/config/BUCK
index 7b2a79b..dac73d6 100644
--- a/apps/config/BUCK
+++ b/apps/config/BUCK
@@ -9,6 +9,7 @@
'//lib:onos-yang-model',
'//core/store/serializers:onos-core-serializers',
'//cli:onos-cli',
+ '//lib:commons-text',
]
osgi_jar_with_tests (
diff --git a/apps/config/pom.xml b/apps/config/pom.xml
index bf6844a..ac06548 100644
--- a/apps/config/pom.xml
+++ b/apps/config/pom.xml
@@ -25,7 +25,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>onos-apps-config</artifactId>
<packaging>bundle</packaging>
- <description>Dynamic Config App6</description>
+ <description>Dynamic Config App</description>
<properties>
<onos.app.name>org.onosproject.configapp</onos.app.name>
<onos.app.category>Utility</onos.app.category>
@@ -71,8 +71,12 @@
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ </dependency>
+
</dependencies>
-
-
</project>
diff --git a/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java b/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
index 358b400..6e517a0 100644
--- a/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
+++ b/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
@@ -44,6 +44,7 @@
*/
public static final String DCS_NAMESPACE = "org.onosproject.dcs";
+ // FIXME '/' is problematic name from RFC 7950/7951 point of view
/**
* SchemaId name for root node.
*/
diff --git a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
index 95c8276..bff42b5 100644
--- a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
+++ b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
@@ -16,18 +16,28 @@
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;
@@ -40,7 +50,15 @@
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,
@@ -186,9 +204,239 @@
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();
+ }
+
}
diff --git a/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java b/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
index b1c237b..0c9623e 100644
--- a/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
+++ b/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
@@ -15,10 +15,15 @@
*/
package org.onosproject.d.config;
+import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.onosproject.d.config.DeviceResourceIds.DCS_NAMESPACE;
+import static org.onosproject.d.config.DeviceResourceIds.DEVICES_ID;
+import static org.onosproject.d.config.DeviceResourceIds.toResourceId;
+import static org.onosproject.d.config.ResourceIds.fromInstanceIdentifier;
import org.junit.Test;
+import org.onosproject.net.DeviceId;
import org.onosproject.yang.model.ResourceId;
public class ResourceIdsTest {
@@ -29,6 +34,64 @@
.build();
@Test
+ public void testFromInstanceIdentifier() {
+
+ ResourceId eth0 = ResourceId.builder()
+ .addBranchPointSchema("interfaces", "ietf-interfaces")
+ .addBranchPointSchema("interface", "ietf-interfaces")
+ .addKeyLeaf("name", "ietf-interfaces", "eth0")
+ .build();
+ assertThat(ResourceIds.fromInstanceIdentifier("/ietf-interfaces:interfaces/interface[name=\"eth0\"]"),
+ is(eth0));
+
+ assertThat("fromInstanceIdentifier return path relative to virtual root",
+ ResourceIds.fromInstanceIdentifier("/org.onosproject.dcs:devices"),
+ is(ResourceIds.relativize(ResourceIds.ROOT_ID, DEVICES_ID)));
+
+ assertThat(ResourceIds.prefixDcsRoot(
+ ResourceIds.fromInstanceIdentifier("/org.onosproject.dcs:devices")),
+ is(DEVICES_ID));
+
+ assertThat(ResourceIds.fromInstanceIdentifier("/"),
+ is(nullValue()));
+
+ DeviceId deviceId = DeviceId.deviceId("test:device-identifier");
+ assertThat(ResourceIds.prefixDcsRoot(
+ fromInstanceIdentifier("/org.onosproject.dcs:devices/device[device-id=\"test:device-identifier\"]")),
+ is(toResourceId(deviceId)));
+
+ }
+
+ @Test
+ public void testToInstanceIdentifier() {
+
+ assertThat(ResourceIds.toInstanceIdentifier(ResourceIds.ROOT_ID),
+ is("/"));
+ assertThat(ResourceIds.toInstanceIdentifier(DEVICES_ID),
+ is("/org.onosproject.dcs:devices"));
+
+ DeviceId deviceId = DeviceId.deviceId("test:device-identifier");
+ assertThat(ResourceIds.toInstanceIdentifier(toResourceId(deviceId)),
+ is("/org.onosproject.dcs:devices/device[device-id=\"test:device-identifier\"]"));
+
+ assertThat(ResourceIds.toInstanceIdentifier(ResourceIds.relativize(DEVICES_ID, toResourceId(deviceId))),
+ is("/org.onosproject.dcs:device[device-id=\"test:device-identifier\"]"));
+
+ ResourceId eth0 = ResourceId.builder()
+ .addBranchPointSchema("interfaces", "ietf-interfaces")
+ .addBranchPointSchema("interface", "ietf-interfaces")
+ .addKeyLeaf("name", "ietf-interfaces", "eth0")
+ .build();
+ assertThat(ResourceIds.toInstanceIdentifier(eth0),
+ is("/ietf-interfaces:interfaces/interface[name=\"eth0\"]"));
+
+
+ assertThat(ResourceIds.toInstanceIdentifier(ResourceIds.concat(toResourceId(deviceId), eth0)),
+ is("/org.onosproject.dcs:devices/device[device-id=\"test:device-identifier\"]"
+ + "/ietf-interfaces:interfaces/interface[name=\"eth0\"]"));
+ }
+
+ @Test
public void testConcat() {
ResourceId devices = ResourceId.builder()
.addBranchPointSchema(DeviceResourceIds.DEVICES_NAME,