Add device proxy support to RESTCONF

Change-Id: I2e309ca7c5f7e2a183a5f2cef11627286647d6b7
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 a08c04a..95c8276 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
@@ -15,12 +15,7 @@
  */
 package org.onosproject.d.config;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Iterator;
-import java.util.Objects;
-
+import com.google.common.annotations.Beta;
 import org.onosproject.yang.model.DataNode;
 import org.onosproject.yang.model.KeyLeaf;
 import org.onosproject.yang.model.LeafListKey;
@@ -30,7 +25,11 @@
 import org.onosproject.yang.model.SchemaId;
 import org.slf4j.Logger;
 
-import com.google.common.annotations.Beta;
+import java.util.Iterator;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Utility related to ResourceId.
@@ -52,7 +51,7 @@
      * Builds the ResourceId of specified {@code node}.
      *
      * @param parent ResourceId of {@code node} parent
-     * @param node to create ResourceId.
+     * @param node   to create ResourceId.
      * @return ResourceId of {@code node}
      */
     public static ResourceId resourceId(ResourceId parent, DataNode node) {
@@ -69,27 +68,27 @@
 
         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_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 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;
+            case SINGLE_INSTANCE_LEAF_VALUE_NODE:
+            case SINGLE_INSTANCE_NODE:
+                builder.addBranchPointSchema(sid.name(), sid.namespace());
+                break;
 
-        default:
-            throw new IllegalArgumentException("Unknown type " + node);
+            default:
+                throw new IllegalArgumentException("Unknown type " + node);
 
         }
 
@@ -100,7 +99,7 @@
      * Concats {@code path} after {@code prefix}.
      *
      * @param prefix path
-     * @param path to append after {@code path}
+     * @param path   to append after {@code path}
      * @return concatenated ResouceId
      */
     public static ResourceId concat(ResourceId prefix, ResourceId path) {
@@ -118,7 +117,7 @@
     /**
      * Returns {@code child} as relative ResourceId against {@code base}.
      *
-     * @param base ResourceId
+     * @param base  ResourceId
      * @param child ResourceId to relativize
      * @return relative ResourceId
      */
@@ -135,7 +134,7 @@
 
             checkArgument(Objects.equals(b, c),
                           "%s is not a prefix of %s.\n" +
-                          "b:%s != c:%s",
+                                  "b:%s != c:%s",
                           base, child,
                           b, c);
         }
@@ -145,16 +144,46 @@
     }
 
     /**
+     * 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
+     * @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()));
+                prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size()));
     }
 
     public static boolean startsWithRootNode(ResourceId path) {
diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java
new file mode 100644
index 0000000..ce4c5cd
--- /dev/null
+++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java
@@ -0,0 +1,188 @@
+/*
+ * 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.restconf.restconfmanager;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.yang.model.ResourceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.UriBuilder;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+
+import static org.onosproject.d.config.DeviceResourceIds.toResourceId;
+import static org.onosproject.d.config.ResourceIds.concat;
+import static org.onosproject.d.config.ResourceIds.removeRootNode;
+import static org.onosproject.restconf.utils.RestconfUtils.convertUriToRid;
+
+/**
+ * Representation of the data resource identifiers used by the RESTCONF manager.
+ * <p>
+ * For a data resource under the device hierarchy, the restconf manager needs
+ * to maintain 2 separate resource IDs, one used by the
+ * the Dynamic Config, and the other by the Yang
+ * Runtime. (i.e., The resource IDs used by the dyn-config contain the
+ * "/devices/device" prefix, whereas the ones used by Yang Runtime do not.)
+ * This class provides the interface for the RESTCONF manager to use these
+ * 2 resource IDs.
+ */
+public final class DataResourceLocator {
+
+    private static final Logger log = LoggerFactory.getLogger(DataResourceLocator.class);
+
+    private static final String DATA_ROOT_DIR = "/onos/restconf/data";
+    private static final String DEVICE_REGEX = "/devices/device=[^/]+";
+    private static final String DEVICE_URI_PREFIX = DATA_ROOT_DIR + "/devices/device=";
+
+    /**
+     * The resource ID used by Yang Runtime to refer to
+     * a data node.
+     */
+    private ResourceId yrtResourceId;
+
+    /**
+     * The resource ID used by the Dynamic Config to refer
+     * to a data node.
+     */
+    private ResourceId dcsResourceId;
+
+    /**
+     * URI used by RESTCONF to refer to a data node.
+     */
+    private URI uriForRestconf;
+
+    /**
+     * URI used by Yang Runtime to refer to a data node.
+     */
+    private URI uriForYangRuntime;
+
+    // Suppresses default constructor, ensuring non-instantiability.
+    private DataResourceLocator() {
+    }
+
+    private DataResourceLocator(ResourceId yrtResourceId,
+                                ResourceId dcsResourceId,
+                                URI uriForRestconf,
+                                URI uriForYangRuntime) {
+        this.yrtResourceId = yrtResourceId;
+        this.dcsResourceId = dcsResourceId;
+        this.uriForRestconf = uriForRestconf;
+        this.uriForYangRuntime = uriForYangRuntime;
+    }
+
+    public ResourceId ridForDynConfig() {
+        return dcsResourceId;
+    }
+
+    public ResourceId ridForYangRuntime() {
+        return yrtResourceId;
+    }
+
+    public URI uriForRestconf() {
+        return uriForRestconf;
+    }
+
+    public URI uriForYangRuntime() {
+        return uriForYangRuntime;
+    }
+
+    /**
+     * Creates a DataResourceLocator object based on a given URI.
+     *
+     * @param uri given URI
+     * @return instantiated DataResourceLocator object
+     */
+    public static DataResourceLocator newInstance(URI uri) {
+        URI uriForYangRuntime = uriForYangRuntime(uri);
+        ResourceId yrtResourceId = ridForYangRuntime(uriForYangRuntime);
+        /*
+         * If the given URI starts with "devices/device" prefix, then form the
+         * resource ID used by dyn-config by adding the prefix to the resource ID
+         * used by YANG runtime. Otherwise the two resource IDs are the same.
+         */
+        ResourceId dcsResourceId = isDeviceResource(uri) ?
+                addDevicePrefix(yrtResourceId, getDeviceId(uri)) : yrtResourceId;
+
+        return new DataResourceLocator(yrtResourceId, dcsResourceId,
+                                       uri, uriForYangRuntime);
+    }
+
+    private static URI uriForYangRuntime(URI uriForRestconf) {
+        return isDeviceResource(uriForRestconf) ?
+                removeDeviceProxyPrefix(uriForRestconf) : uriForRestconf;
+    }
+
+    private static ResourceId ridForYangRuntime(URI uriForYangRuntime) {
+        ResourceId yrtResourceId = convertUriToRid(uriForYangRuntime);
+        if (yrtResourceId == null) {
+            yrtResourceId = ResourceId.builder().addBranchPointSchema("/", null).build();
+        }
+
+        return yrtResourceId;
+    }
+
+    private static URI removeDeviceProxyPrefix(URI uri) {
+        if (uri == null) {
+            return null;
+        }
+        UriBuilder builder = UriBuilder.fromUri(uri);
+        String newPath = rmDeviceStr(uri.getRawPath());
+        builder.replacePath(newPath);
+
+        return builder.build();
+    }
+
+    private static String rmDeviceStr(String uriStr) {
+        if (uriStr == null) {
+            return null;
+
+        }
+        return uriStr.replaceFirst(DEVICE_REGEX, "");
+    }
+
+    private static DeviceId getDeviceId(URI uri) {
+        return DeviceId.deviceId(deviceIdStr(uri.getRawPath()));
+    }
+
+    private static String deviceIdStr(String rawPath) {
+        String[] segments = rawPath.split("/");
+        try {
+            for (String s : segments) {
+                if (s.startsWith("device=")) {
+                    return URLDecoder.decode(s.substring("device=".length()), "utf-8");
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            log.error("deviceIdStr: caught UnsupportedEncodingException");
+            log.debug("deviceIdStr: ", e);
+        }
+        return null;
+    }
+
+    private static ResourceId addDevicePrefix(ResourceId rid, DeviceId did) {
+        return concat(toResourceId(did), removeRootNode(rid));
+    }
+
+    private static boolean isDeviceResource(URI uri) {
+        if (uri == null) {
+            return false;
+        }
+        return uri.getRawPath().startsWith(DEVICE_URI_PREFIX);
+    }
+}
diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
index 356306d..5334cd0 100644
--- a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
+++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java
@@ -54,9 +54,9 @@
 import java.util.concurrent.Executors;
 
 import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static org.onosproject.d.config.ResourceIds.parentOf;
 import static org.onosproject.restconf.utils.RestconfUtils.convertDataNodeToJson;
 import static org.onosproject.restconf.utils.RestconfUtils.convertJsonToDataNode;
-import static org.onosproject.restconf.utils.RestconfUtils.convertUriToRid;
 import static org.onosproject.restconf.utils.RestconfUtils.rmLastPathSegment;
 import static org.onosproject.yang.model.DataNode.Type.MULTI_INSTANCE_NODE;
 import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE;
@@ -107,29 +107,30 @@
     @Override
     public ObjectNode runGetOperationOnDataResource(URI uri)
             throws RestconfException {
-        ResourceId rid = convertUriToRid(uri);
+        DataResourceLocator rl = DataResourceLocator.newInstance(uri);
         // TODO: define Filter (if there is any requirement).
         Filter filter = Filter.builder().build();
         DataNode dataNode;
 
         try {
-            if (!dynamicConfigService.nodeExist(rid)) {
+            if (!dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
                 return null;
             }
-            dataNode = dynamicConfigService.readNode(rid, filter);
+            dataNode = dynamicConfigService.readNode(rl.ridForDynConfig(), filter);
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
             throw new RestconfException("ERROR: DynamicConfigService",
                                         INTERNAL_SERVER_ERROR);
         }
-        ObjectNode rootNode = convertDataNodeToJson(rid, dataNode);
+        ObjectNode rootNode = convertDataNodeToJson(rl.ridForYangRuntime(), dataNode);
         return rootNode;
     }
 
     @Override
     public void runPostOperationOnDataResource(URI uri, ObjectNode rootNode)
             throws RestconfException {
-        ResourceData receivedData = convertJsonToDataNode(uri, rootNode);
+        DataResourceLocator rl = DataResourceLocator.newInstance(uri);
+        ResourceData receivedData = convertJsonToDataNode(rl.uriForYangRuntime(), rootNode);
         ResourceId rid = receivedData.resourceId();
         List<DataNode> dataNodeList = receivedData.dataNodes();
         if (dataNodeList.size() > 1) {
@@ -143,7 +144,7 @@
         }
 
         try {
-            dynamicConfigService.createNode(rid, dataNode);
+            dynamicConfigService.createNode(rl.ridForDynConfig(), dataNode);
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
             throw new RestconfException("ERROR: DynamicConfigService",
@@ -154,9 +155,8 @@
     @Override
     public void runPutOperationOnDataResource(URI uri, ObjectNode rootNode)
             throws RestconfException {
-        ResourceId rid = convertUriToRid(uri);
-        ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(uri), rootNode);
-        ResourceId parentRid = receivedData.resourceId();
+        DataResourceLocator rl = DataResourceLocator.newInstance(uri);
+        ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode);
         List<DataNode> dataNodeList = receivedData.dataNodes();
         if (dataNodeList.size() > 1) {
             log.warn("There are more than one Data Node can be proceed: {}", dataNodeList.size());
@@ -168,10 +168,10 @@
              * If the data node already exists, then replace it.
              * Otherwise, create it.
              */
-            if (dynamicConfigService.nodeExist(rid)) {
-                dynamicConfigService.replaceNode(parentRid, dataNode);
+            if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
+                dynamicConfigService.replaceNode(parentOf(rl.ridForDynConfig()), dataNode);
             } else {
-                dynamicConfigService.createNode(parentRid, dataNode);
+                dynamicConfigService.createNode(parentOf(rl.ridForDynConfig()), dataNode);
             }
 
         } catch (FailedException e) {
@@ -184,10 +184,10 @@
     @Override
     public void runDeleteOperationOnDataResource(URI uri)
             throws RestconfException {
-        ResourceId rid = convertUriToRid(uri);
+        DataResourceLocator rl = DataResourceLocator.newInstance(uri);
         try {
-            if (dynamicConfigService.nodeExist(rid)) {
-                dynamicConfigService.deleteNode(rid);
+            if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
+                dynamicConfigService.deleteNode(rl.ridForDynConfig());
             }
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
@@ -199,7 +199,8 @@
     @Override
     public void runPatchOperationOnDataResource(URI uri, ObjectNode rootNode)
             throws RestconfException {
-        ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(uri), rootNode);
+        DataResourceLocator rl = DataResourceLocator.newInstance(uri);
+        ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode);
         ResourceId rid = receivedData.resourceId();
         List<DataNode> dataNodeList = receivedData.dataNodes();
         if (dataNodeList.size() > 1) {
@@ -213,7 +214,7 @@
         }
 
         try {
-            dynamicConfigService.updateNode(rid, dataNode);
+            dynamicConfigService.updateNode(parentOf(rl.ridForDynConfig()), dataNode);
         } catch (FailedException e) {
             log.error("ERROR: DynamicConfigService: ", e);
             throw new RestconfException("ERROR: DynamicConfigService",
diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
index e14477f..cd0b428 100644
--- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
+++ b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java
@@ -107,7 +107,8 @@
     }
 
     /**
-     * Convert URI to ResourceId.
+     * Convert URI to ResourceId. If the URI represents the datastore resource
+     * (i.e., the root of datastore), a null is returned.
      *
      * @param uri URI of the data resource
      * @return resource identifier