| /* |
| * 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.yang.runtime; |
| |
| import org.onosproject.yang.compiler.datamodel.YangLeaf; |
| import org.onosproject.yang.compiler.datamodel.YangLeafList; |
| import org.onosproject.yang.compiler.datamodel.YangSchemaNode; |
| import org.onosproject.yang.compiler.datamodel.exceptions.DataModelException; |
| import org.onosproject.yang.model.DataNode; |
| import org.onosproject.yang.model.InnerNode; |
| import org.onosproject.yang.model.LeafNode; |
| import org.onosproject.yang.model.LeafSchemaContext; |
| import org.onosproject.yang.model.ListSchemaContext; |
| import org.onosproject.yang.model.ResourceId; |
| import org.onosproject.yang.model.SchemaContext; |
| import org.onosproject.yang.model.SchemaId; |
| import org.onosproject.yang.model.SingleInstanceNodeContext; |
| import org.onosproject.yang.runtime.impl.DefaultYangModelRegistry; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static org.onosproject.yang.model.DataNode.Builder; |
| import static org.onosproject.yang.model.DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE; |
| import static org.onosproject.yang.model.DataNode.Type.MULTI_INSTANCE_NODE; |
| import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE; |
| import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_NODE; |
| |
| /** |
| * Representation of serializer helper utilities, serializer can use them to |
| * build the data node and resource identifier without obtaining the schema |
| * context. |
| */ |
| public final class SerializerHelper { |
| |
| // Serializer helper formatted error string |
| private static final String FMT_TOO_FEW = |
| "Too few key parameters in %s. Expected %d; actual %d."; |
| private static final String FMT_TOO_MANY = |
| "Too many key parameters in %s. Expected %d; actual %d."; |
| private static final String FMT_NOT_EXIST = |
| "Schema node with name %s doesn't exist."; |
| private static final String E_NAMESPACE = |
| "NameSpace is mandatory to provide for first level node."; |
| private static final String E_LEAFLIST = |
| "Method is not allowed to pass multiple values for leaf-list."; |
| private static final String E_RESID = "Invalid resourceId builder."; |
| |
| // Name for first level child |
| private static final String SLASH = "/"; |
| |
| // Forbid construction. |
| private SerializerHelper() { |
| } |
| |
| /** |
| * Initializes resource identifier builder with YANG serializer context |
| * information. |
| * |
| * @param context YANG serializer context |
| * @return resource identifier builder |
| */ |
| public static ResourceId.Builder initializeResourceId( |
| YangSerializerContext context) { |
| SchemaContext cont = context.getContext(); |
| SchemaId id = cont.getSchemaId(); |
| ResourceId.Builder rIdBdr = ResourceId.builder(); |
| rIdBdr.addBranchPointSchema(id.name(), id.namespace()); |
| // Adding the schema context to resource id app info. |
| rIdBdr.appInfo(cont); |
| return rIdBdr; |
| } |
| |
| /** |
| * Adds to resource identifier builder. |
| * <p> |
| * Builder and name are mandatory inputs, In case namespace is null, |
| * namespace of last key in the keylist of resource identifier builder will |
| * be used. Value should only be provided for leaf-list/list. |
| * <p> |
| * This API will also carry out necessary schema related validations. |
| * |
| * @param builder resource identifier builder |
| * @param name name of node |
| * @param namespace namespace of node |
| * @param value value of node |
| * @return resource identifier builder |
| * @throws IllegalArgumentException when given input is not as per the |
| * schema context |
| * @throws IllegalStateException when a key is added under a atomic child |
| */ |
| public static ResourceId.Builder addToResourceId( |
| ResourceId.Builder builder, String name, String namespace, |
| String value) { |
| try { |
| SchemaContext parentCont = (SchemaContext) builder.appInfo(); |
| SchemaContext child = getChildSchemaContext( |
| parentCont, name, namespace); |
| if (child == null) { |
| throw new IllegalArgumentException( |
| errorMsg(FMT_NOT_EXIST, name)); |
| } |
| DataNode.Type type = child.getType(); |
| updateResourceId(builder, name, value, child, type); |
| if (type == SINGLE_INSTANCE_LEAF_VALUE_NODE && |
| ((YangLeaf) child).isKeyLeaf()) { |
| builder.appInfo(parentCont); |
| } |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| return builder; |
| } |
| |
| /** |
| * Adds to resource identifier builder, this API will be used by |
| * applications which are not aware about the schema name association |
| * with key's value. |
| * <p> |
| * Builder and name are mandatory inputs, In case namespace is null, |
| * namespace of last key in the key list of resource identifier builder will |
| * be used. Value should only be provided for leaf-list/list. |
| * <p> |
| * In case of list its mandatory to pass either all key values of list or |
| * non of them (wild card to support get operation). |
| * <p> |
| * This API will also carry out necessary schema related validations. |
| * |
| * @param builder resource identifier builder |
| * @param name name of node |
| * @param namespace namespace of node |
| * @param value ordered list of values |
| * @return resource identifier builder |
| * @throws IllegalArgumentException when given input is not as per the |
| * schema context |
| * @throws IllegalStateException when a key is added under a atomic child |
| */ |
| public static ResourceId.Builder addToResourceId( |
| ResourceId.Builder builder, String name, String namespace, |
| List<String> value) throws IllegalArgumentException { |
| Object valObject; |
| SchemaContext child = getChildSchemaContext( |
| (SchemaContext) builder.appInfo(), name, namespace); |
| namespace = child.getSchemaId().namespace(); |
| DataNode.Type childType = child.getType(); |
| try { |
| if (childType == MULTI_INSTANCE_LEAF_VALUE_NODE) { |
| if (value.size() > 1) { |
| throw new IllegalArgumentException(errorMsg(E_LEAFLIST)); |
| } |
| valObject = ((LeafSchemaContext) child).fromString( |
| value.get(0)); |
| builder.addLeafListBranchPoint(name, namespace, valObject); |
| } else if (childType == MULTI_INSTANCE_NODE) { |
| // Adding list node. |
| String v = null; |
| builder = addToResourceId(builder, name, namespace, v); |
| if (value != null && value.size() != 0) { |
| Set<String> keyLeafs = ((ListSchemaContext) child) |
| .getKeyLeaf(); |
| try { |
| checkElementCount(name, keyLeafs.size(), value.size()); |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| |
| // After validation adding the key nodes under the list node. |
| Iterator<String> sklIter = keyLeafs.iterator(); |
| Iterator<String> kvlIter = value.iterator(); |
| String keyEleName; |
| |
| while (kvlIter.hasNext()) { |
| String val = kvlIter.next(); |
| keyEleName = sklIter.next(); |
| SchemaContext keyChild = getChildSchemaContext( |
| (SchemaContext) builder.appInfo(), keyEleName, |
| namespace); |
| valObject = ((LeafSchemaContext) keyChild).fromString(val); |
| builder.addKeyLeaf(keyEleName, namespace, valObject); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException( |
| errorMsg(FMT_NOT_EXIST, name)); |
| } |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| |
| builder.appInfo(child); |
| return builder; |
| } |
| |
| /** |
| * Initializes a new data node builder using resource identifier builder. |
| * <p> |
| * This API can only be used when passed parameter resourceId builder is |
| * prepared with the help of serializer helper utility. |
| * |
| * @param builder resource identifier builder |
| * @return data node builder |
| */ |
| public static Builder initializeDataNode(ResourceId.Builder builder) { |
| |
| ExtResourceIdBldr rIdBldr = new ExtResourceIdBldr(); |
| rIdBldr = rIdBldr.copyBuilder(rIdBldr, builder.build()); |
| rIdBldr.appInfo(builder.appInfo()); |
| SchemaContext node = (SchemaContext) builder.appInfo(); |
| HelperContext info = new HelperContext(); |
| info.setResourceIdBuilder(null); |
| info.setParentResourceIdBldr(rIdBldr); |
| SchemaId sId = node.getSchemaId(); |
| // Creating a dummy node |
| InnerNode.Builder dBldr = InnerNode.builder( |
| sId.name(), sId.namespace()).type(node.getType()); |
| dBldr.appInfo(info); |
| return dBldr; |
| } |
| |
| /** |
| * Initializes a new data node builder. |
| * |
| * @param context YANG serializer context |
| * @return data node builder |
| */ |
| public static Builder initializeDataNode(YangSerializerContext context) { |
| |
| SchemaContext node = context.getContext(); |
| SchemaId sId = node.getSchemaId(); |
| HelperContext info = new HelperContext(); |
| ExtResourceIdBldr rId = info.getResourceIdBuilder(); |
| rId.addBranchPointSchema(sId.name(), sId.namespace()); |
| rId.appInfo(node); |
| info.setResourceIdBuilder(rId); |
| InnerNode.Builder dBlr = InnerNode.builder(sId.name(), sId.namespace()); |
| dBlr.type(SINGLE_INSTANCE_NODE); |
| dBlr.appInfo(info); |
| return dBlr; |
| } |
| |
| /** |
| * Adds a data node to a given data node builder. |
| * <p> |
| * Name and builder is mandatory inputs. If namespace is not provided |
| * parents namespace will be added for data node. Value should be |
| * provided for leaf/leaf-list. In case of leaf-list it's expected that this |
| * API is called for each leaf-list instance. Callers aware about the node |
| * type can opt to provide data node type, implementation will carry out |
| * validations based on input type and obtained type. |
| * <p> |
| * This API will also carry out necessary schema related validations. |
| * |
| * @param builder data node builder |
| * @param name name of data node |
| * @param namespace namespace of data node |
| * @param value value of data node |
| * @param type type of data node |
| * @return data node builder with added information |
| * @throws IllegalArgumentException when given input is not as per the |
| * schema context |
| * @throws IllegalStateException when a key is added under a atomic child |
| */ |
| public static Builder addDataNode(Builder builder, |
| String name, String namespace, |
| String value, DataNode.Type type) { |
| try { |
| Object valObject; |
| SchemaContext node; |
| ExtResourceIdBldr rIdBldr; |
| HelperContext nodeInfo; |
| boolean initWithRId = false; |
| HelperContext info = (HelperContext) builder.appInfo(); |
| ExtResourceIdBldr curBldr = info.getResourceIdBuilder(); |
| |
| if (curBldr != null) { |
| rIdBldr = info.getResourceIdBuilder(); |
| node = (SchemaContext) rIdBldr.appInfo(); |
| nodeInfo = new HelperContext(); |
| initWithRId = true; |
| } else { |
| // If data node is initialized by resource id. |
| node = (SchemaContext) info.getParentResourceIdBldr().appInfo(); |
| rIdBldr = info.getParentResourceIdBldr(); |
| nodeInfo = info; |
| } |
| |
| SchemaContext childSchema = getChildSchemaContext(node, name, |
| namespace); |
| DataNode.Type nodeType = childSchema.getType(); |
| |
| if (type != null && !nodeType.equals(type)) { |
| throw new IllegalArgumentException( |
| errorMsg(FMT_NOT_EXIST, name)); |
| } |
| |
| // Updating the namespace |
| namespace = childSchema.getSchemaId().namespace(); |
| updateResourceId(rIdBldr, name, value, childSchema, nodeType); |
| |
| if (!initWithRId) { |
| /* |
| * Adding first data node in case of if data node initialized |
| * with resource id builder. |
| */ |
| // TODO check based on type, handle leaf without value scenario |
| // also handle list without key leaf scenario. |
| switch (nodeType) { |
| |
| case SINGLE_INSTANCE_LEAF_VALUE_NODE: |
| if (((YangLeaf) childSchema).isKeyLeaf()) { |
| throw new IllegalArgumentException(E_RESID); |
| } |
| valObject = getLeaf(value, childSchema); |
| builder = LeafNode.builder(name, namespace).type(nodeType) |
| .value(valObject); |
| break; |
| case MULTI_INSTANCE_LEAF_VALUE_NODE: |
| valObject = getLeafList(value, childSchema); |
| builder = LeafNode.builder(name, namespace).type(nodeType) |
| .value(valObject); |
| builder = builder.addLeafListValue(valObject); |
| break; |
| default: |
| /* |
| * Can't update the node key in dummy data node as |
| * keybuilder will be initialized only once when |
| * InnerNode.builder call is made with name and namespace. |
| */ |
| builder = InnerNode.builder(name, namespace).type(nodeType); |
| break; |
| } |
| } else { |
| switch (nodeType) { |
| case SINGLE_INSTANCE_LEAF_VALUE_NODE: |
| valObject = getLeaf(value, childSchema); |
| if (((YangLeaf) childSchema).isKeyLeaf()) { |
| builder = builder.addKeyLeaf( |
| name, namespace, valObject); |
| } |
| builder = builder.createChildBuilder( |
| name, namespace, valObject).type(nodeType); |
| break; |
| case MULTI_INSTANCE_LEAF_VALUE_NODE: |
| valObject = getLeafList(value, childSchema); |
| builder = builder.createChildBuilder( |
| name, namespace, valObject).type(nodeType); |
| builder = builder.addLeafListValue(valObject); |
| break; |
| default: |
| builder = builder.createChildBuilder(name, namespace) |
| .type(nodeType); |
| } |
| } |
| |
| nodeInfo.setResourceIdBuilder(rIdBldr); |
| builder.appInfo(nodeInfo); |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| return builder; |
| } |
| |
| /** |
| * Returns the corresponding datatype value object for given leaf-list |
| * value. |
| * |
| * @param val value in string |
| * @param ctx schema context |
| * @return object of value |
| * @throws IllegalArgumentException a violation of data type rules |
| */ |
| private static Object getLeafList(String val, SchemaContext ctx) |
| throws IllegalArgumentException { |
| LeafSchemaContext schema; |
| try { |
| schema = (LeafSchemaContext) ctx; |
| ((YangLeafList) schema).getDataType().isValidValue(val); |
| } catch (DataModelException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| return schema.fromString(val); |
| } |
| |
| /** |
| * Returns the corresponding datatype value object for given leaf value. |
| * |
| * @param val value in string |
| * @param ctx schema context |
| * @return object of value |
| * @throws IllegalArgumentException a violation of data type rules |
| */ |
| private static Object getLeaf(String val, SchemaContext ctx) |
| throws IllegalArgumentException { |
| LeafSchemaContext schema; |
| try { |
| schema = (LeafSchemaContext) ctx; |
| ((YangLeaf) schema).getDataType().isValidValue(val); |
| } catch (DataModelException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| return schema.fromString(val); |
| } |
| |
| /** |
| * Returns resource identifier for a given data node. This API will |
| * be used by serializer to obtain the resource identifier in the |
| * scenario when an annotation is associated with a given data node. |
| * |
| * @param builder data node builder |
| * @return resource identifier of the data node |
| */ |
| public static ResourceId getResourceId(Builder builder) { |
| HelperContext info = (HelperContext) builder.appInfo(); |
| if (info.getParentResourceIdBldr() != null) { |
| return info.getParentResourceIdBldr().getResourceId(); |
| } |
| return info.getResourceIdBuilder().getResourceId(); |
| } |
| |
| /** |
| * Exits a given data node builder. It builds current data node, |
| * adds it to parent's data node builder and returns parent builder. |
| * <p> |
| * In case current data node is topmost node (which was created using |
| * last key of resource identifier), current data node will not be |
| * built and null will be returned, in such case caller is expected to |
| * build data node from builder. |
| * <p> |
| * This API will also carry out necessary exit time validations, for |
| * an example validation about all key leafs presence for a list. |
| * |
| * @param builder data node builder |
| * @return parent builder |
| */ |
| public static Builder exitDataNode(Builder builder) { |
| HelperContext info = (HelperContext) builder.appInfo(); |
| ExtResourceIdBldr rId = info.getResourceIdBuilder(); |
| SchemaContext cont = (SchemaContext) rId.appInfo(); |
| // Deleting the last key entry in resource id. |
| if (cont.getType() == SINGLE_INSTANCE_LEAF_VALUE_NODE) { |
| if (!((YangLeaf) cont).isKeyLeaf()) { |
| rId.traveseToParent(); |
| } |
| } else { |
| rId.traveseToParent(); |
| } |
| rId.appInfo(cont.getParentContext()); |
| return builder.exitNode(); |
| } |
| |
| /** |
| * Returns child schema context for request name and namespace from given |
| * resourceId builder. |
| * |
| * @param context parent schema context |
| * @param name name of the child node |
| * @param namespace namespace of the child node |
| * @return schema context |
| */ |
| public static SchemaContext getChildSchemaContext( |
| SchemaContext context, String name, String namespace) |
| throws IllegalArgumentException { |
| SchemaContext child; |
| SchemaId parentId = context.getSchemaId(); |
| if (namespace == null && parentId.name().equals(SLASH)) { |
| throw new IllegalArgumentException(E_NAMESPACE); |
| } else if (namespace == null) { |
| namespace = parentId.namespace(); |
| } |
| |
| SchemaId id = new SchemaId(name, namespace); |
| child = ((SingleInstanceNodeContext) context).getChildContext(id); |
| if (child == null) { |
| throw new IllegalArgumentException(errorMsg(FMT_NOT_EXIST, name)); |
| } |
| return child; |
| } |
| |
| /** |
| * Checks the user supplied list of argument match's the expected value |
| * or not. |
| * |
| * @param name name of the parent list/leaf-list node |
| * @param expected count suppose to be |
| * @param actual user supplied values count |
| * @throws IllegalArgumentException when user requested multi instance node |
| * instance's count doesn't fit into the |
| * allowed instance limit |
| */ |
| private static void checkElementCount(String name, int expected, int actual) |
| throws IllegalArgumentException { |
| if (expected < actual) { |
| throw new IllegalArgumentException( |
| errorMsg(FMT_TOO_MANY, name, expected, actual)); |
| } else if (expected > actual) { |
| throw new IllegalArgumentException( |
| errorMsg(FMT_TOO_FEW, name, expected, actual)); |
| } |
| } |
| |
| /** |
| * Updates running resource id for current provided builder. |
| * |
| * @param builder resource identifier builder |
| * @param name name of node |
| * @param value value of node |
| * @param child child schema context |
| * @param type type of data node |
| */ |
| private static void updateResourceId( |
| ResourceId.Builder builder, String name, String value, |
| SchemaContext child, DataNode.Type type) |
| throws IllegalArgumentException { |
| |
| Object valObject; |
| switch (type) { |
| case SINGLE_INSTANCE_LEAF_VALUE_NODE: |
| if (((YangLeaf) child).isKeyLeaf()) { |
| valObject = ((LeafSchemaContext) child).fromString(value); |
| builder.addKeyLeaf(name, child.getSchemaId().namespace(), |
| valObject); |
| } else { |
| builder.addBranchPointSchema(name, child.getSchemaId() |
| .namespace()); |
| } |
| break; |
| case MULTI_INSTANCE_LEAF_VALUE_NODE: |
| valObject = ((LeafSchemaContext) child).fromString(value); |
| builder.addLeafListBranchPoint(name, child.getSchemaId() |
| .namespace(), valObject); |
| break; |
| case MULTI_INSTANCE_NODE: |
| case SINGLE_INSTANCE_NODE: |
| if (value == null) { |
| builder.addBranchPointSchema(name, child.getSchemaId() |
| .namespace()); |
| break; |
| } |
| default: |
| throw new IllegalArgumentException( |
| errorMsg(FMT_NOT_EXIST, name)); |
| } |
| builder.appInfo(child); |
| } |
| |
| /** |
| * Returns the error string by filling the parameters in the given |
| * formatted error string. |
| * |
| * @param fmt error format string |
| * @param params parameters to be filled in formatted string |
| * @return error string |
| */ |
| public static String errorMsg(String fmt, Object... params) { |
| return String.format(fmt, params); |
| } |
| |
| /** |
| * Returns the yang module name for given namespace. |
| * |
| * @param c YANG serializer context |
| * @param ns namespace of the module |
| * @return namespace of the module |
| */ |
| public static String getModuleNameFromNameSpace(YangSerializerContext c, |
| String ns) { |
| |
| YangSchemaNode schemaNode = ((DefaultYangModelRegistry) c.getContext()) |
| .getForNameSpace(ns, false); |
| if (schemaNode != null) { |
| return schemaNode.getName(); |
| } |
| return null; |
| } |
| } |