blob: bff42b56e71134323f53e144717cfd91e2d257a8 [file] [log] [blame]
/*
* 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();
}
}