Bug 6746 - Restconf: Not working GET operation on mount points 11/51711/1
authorIvan Hrasko <ivan.hrasko@pantheon.tech>
Mon, 19 Sep 2016 14:22:53 +0000 (16:22 +0200)
committerJakub Toth <jatoth@cisco.com>
Sat, 11 Feb 2017 01:30:59 +0000 (02:30 +0100)
- support for reading data on mount points
- improved merge for config and state data:
when identifiers for data in config and state are the same
then merge must by done at one level down, otherwise state
overrides config or vice versa
- identiers are parsed to identifiers context with reference
to mount points when data are behind mount point
- adapted and added unit tests

Change-Id: I195b3195c0eaa47effd7759e60611a3f420c9a6b
Signed-off-by: Jakub Toth <jatoth@cisco.com>
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/common/wrapper/services/ServicesWrapperImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/jersey/providers/AbstractIdentifierAwareJaxRsProvider.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/rest/services/impl/RestconfModulesServiceImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/rest/services/impl/RestconfOperationsServiceImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/parser/ParserIdentifier.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfModulesServiceTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImplTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java

index 0e7999b2563664506b24f69ab3163737d3f829e1..ce948af3ea1e92101cf6cd19350221fde8a6ebfd 100644 (file)
@@ -138,7 +138,7 @@ public class ServicesWrapperImpl implements BaseServicesWrapper, TransactionServ
 
     @Override
     public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
-            final UriInfo uriInfo) {
+                                           final UriInfo uriInfo) {
         return this.delegRestconfInvokeOpsService.invokeRpc(identifier, payload, uriInfo);
     }
 
@@ -148,14 +148,16 @@ public class ServicesWrapperImpl implements BaseServicesWrapper, TransactionServ
     }
 
     public void setHandlers(final SchemaContextHandler schemaCtxHandler,
-            final DOMMountPointServiceHandler domMountPointServiceHandler,
-            final TransactionChainHandler transactionChainHandler, final DOMDataBrokerHandler domDataBrokerHandler,
-            final RpcServiceHandler rpcServiceHandler) {
+                            final DOMMountPointServiceHandler domMountPointServiceHandler,
+                            final TransactionChainHandler transactionChainHandler,
+                            final DOMDataBrokerHandler domDataBrokerHandler,
+                            final RpcServiceHandler rpcServiceHandler) {
         this.delegRestModService = new RestconfModulesServiceImpl(schemaCtxHandler, domMountPointServiceHandler);
         this.delegRestOpsService = new RestconfOperationsServiceImpl(schemaCtxHandler, domMountPointServiceHandler);
         this.delegRestSchService = new RestconfSchemaServiceImpl(schemaCtxHandler, domMountPointServiceHandler);
         this.delegRestStrsService = new RestconfStreamsServiceImpl(schemaCtxHandler);
-        this.delegRestconfDataService = new RestconfDataServiceImpl(schemaCtxHandler, transactionChainHandler);
+        this.delegRestconfDataService = new RestconfDataServiceImpl(schemaCtxHandler, transactionChainHandler,
+                domMountPointServiceHandler);
         this.delegRestconfInvokeOpsService = new RestconfInvokeOperationsServiceImpl(rpcServiceHandler,
                 schemaCtxHandler);
         this.delegRestconfSubscrService = new RestconfStreamsSubscriptionServiceImpl(domDataBrokerHandler);
index bcf95f7b6450ccb9925316951f773d8fc0b78aa4..518d76631444fd4569eb02e767469280f9bd8759 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.restconf.jersey.providers;
 
+import com.google.common.base.Optional;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Request;
 import javax.ws.rs.core.UriInfo;
@@ -32,7 +33,7 @@ public class AbstractIdentifierAwareJaxRsProvider {
 
     protected InstanceIdentifierContext<?> getInstanceIdentifierContext() {
         return ParserIdentifier.toInstanceIdentifier(getIdentifier(),
-                ControllerContext.getInstance().getGlobalSchema());
+                ControllerContext.getInstance().getGlobalSchema(), Optional.absent());
     }
 
     protected UriInfo getUriInfo() {
index e4bc1b423988e386efef7729538a5b3451d434eb..5b7a6ebd999441b36f456288e8e4632d2b86552b 100644 (file)
@@ -7,12 +7,12 @@
  */
 package org.opendaylight.restconf.rest.services.impl;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.util.Collections;
 import java.util.Set;
 import javax.ws.rs.core.UriInfo;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
-import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
@@ -80,11 +80,9 @@ public class RestconfModulesServiceImpl implements RestconfModulesService {
             throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
         }
         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
-        final InstanceIdentifierContext<?> mountPointIdentifier = ParserIdentifier.toInstanceIdentifier(identifier,
-                schemaContextRef.get());
-        final DOMMountPointService domMointPointService = this.domMountPointServiceHandler.get();
-        final DOMMountPoint mountPoint = domMointPointService
-                .getMountPoint(mountPointIdentifier.getInstanceIdentifier()).get();
+        final InstanceIdentifierContext<?> mountPointIdentifier = ParserIdentifier.toInstanceIdentifier(
+                identifier, schemaContextRef.get(), Optional.of(this.domMountPointServiceHandler.get()));
+        final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
         return getModules(mountPoint.getSchemaContext().getModules(), schemaContextRef, mountPoint);
     }
 
@@ -94,13 +92,15 @@ public class RestconfModulesServiceImpl implements RestconfModulesService {
         Preconditions.checkNotNull(identifier);
         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
         final QName moduleQname = ParserIdentifier.makeQNameFromIdentifier(identifier);
-        Module module = null;
+        final Module module;
         DOMMountPoint mountPoint = null;
         if (identifier.contains(RestconfConstants.MOUNT)) {
-            final InstanceIdentifierContext<?> point = ParserIdentifier.toInstanceIdentifier(identifier,
-                    schemaContextRef.get());
-            final DOMMountPointService domMointPointService = this.domMountPointServiceHandler.get();
-            mountPoint = domMointPointService.getMountPoint(point.getInstanceIdentifier()).get();
+            // we only need to find mount point itself
+            final String mountPointPath = identifier.substring(
+                    0, identifier.indexOf(RestconfConstants.MOUNT) + RestconfConstants.MOUNT.length());
+            final InstanceIdentifierContext<?> mountPointContext = ParserIdentifier.toInstanceIdentifier(
+                    mountPointPath, schemaContextRef.get(), Optional.of(this.domMountPointServiceHandler.get()));
+            mountPoint = mountPointContext.getMountPoint();
             module = schemaContextRef.findModuleInMountPointByQName(mountPoint, moduleQname);
         } else {
             module = schemaContextRef.findModuleByQName(moduleQname);
index bddd086bd702a5427d63c99000a66df654ad74ea..e14fa8fe6bf8658199ca3750e31f21c04193aa2b 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.restconf.rest.services.impl;
 
+import com.google.common.base.Optional;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -73,12 +74,12 @@ public class RestconfOperationsServiceImpl implements RestconfOperationsService
 
     @Override
     public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
-        Set<Module> modules = null;
-        DOMMountPoint mountPoint = null;
+        final Set<Module> modules;
+        final DOMMountPoint mountPoint;
         final SchemaContextRef ref = new SchemaContextRef(this.schemaContextHandler.get());
         if (identifier.contains(RestconfConstants.MOUNT)) {
-            final InstanceIdentifierContext<?> mountPointIdentifier = ParserIdentifier.toInstanceIdentifier(identifier,
-                    ref.get());
+            final InstanceIdentifierContext<?> mountPointIdentifier = ParserIdentifier.toInstanceIdentifier(
+                    identifier, ref.get(), Optional.of(this.domMountPointServiceHandler.get()));
             mountPoint = mountPointIdentifier.getMountPoint();
             modules = ref.getModules(mountPoint);
 
index 95645334cbfacc1d6a2876d86bc9c9158fb707ad..cb17434392c1870ce602082d4719e8366a671abf 100644 (file)
@@ -10,11 +10,12 @@ package org.opendaylight.restconf.restful.services.impl;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.TimeZone;
+import javax.annotation.Nonnull;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
@@ -22,8 +23,10 @@ import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
 import org.opendaylight.restconf.RestConnectorProvider;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
 import org.opendaylight.restconf.handlers.SchemaContextHandler;
 import org.opendaylight.restconf.handlers.TransactionChainHandler;
 import org.opendaylight.restconf.restful.services.api.RestconfDataService;
@@ -49,11 +52,14 @@ public class RestconfDataServiceImpl implements RestconfDataService {
 
     private final SchemaContextHandler schemaContextHandler;
     private final TransactionChainHandler transactionChainHandler;
+    private final DOMMountPointServiceHandler mountPointServiceHandler;
 
     public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler,
-            final TransactionChainHandler transactionChainHandler) {
+                                   final TransactionChainHandler transactionChainHandler,
+                                   final DOMMountPointServiceHandler mountPointServiceHandler) {
         this.schemaContextHandler = schemaContextHandler;
         this.transactionChainHandler = transactionChainHandler;
+        this.mountPointServiceHandler = mountPointServiceHandler;
     }
 
     @Override
@@ -61,28 +67,36 @@ public class RestconfDataServiceImpl implements RestconfDataService {
         Preconditions.checkNotNull(identifier);
         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
 
-        final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(identifier,
-                schemaContextRef.get());
+        final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+                identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
         final String value = uriInfo.getQueryParameters().getFirst(RestconfDataServiceConstant.CONTENT);
 
-        DOMTransactionChain transaction = null;
+        final DOMTransactionChain transactionChain;
         if (mountPoint == null) {
-            transaction = this.transactionChainHandler.get();
+            transactionChain = this.transactionChainHandler.get();
         } else {
-            transaction = transactionOfMountPoint(mountPoint);
+            transactionChain = transactionChainOfMountPoint(mountPoint);
         }
+
         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
-                transaction);
+                transactionChain);
         final NormalizedNode<?, ?> node = ReadDataTransactionUtil.readData(value, transactionNode);
+        if (node == null) {
+            throw new RestconfDocumentedException(
+                    "Request could not be completed because the relevant data model content does not exist",
+                    RestconfError.ErrorType.PROTOCOL,
+                    RestconfError.ErrorTag.DATA_MISSING);
+        }
         final SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
         dateFormatGmt.setTimeZone(TimeZone.getTimeZone("GMT"));
         final String etag = '"' + node.getNodeType().getModule().getFormattedRevision()
                 + node.getNodeType().getLocalName() + '"';
-        Response resp = null;
+        final Response resp;
+
         if ((value == null) || value.contains(RestconfDataServiceConstant.ReadData.CONFIG)) {
             resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).header("ETag", etag)
-                    .header("Last-Modified", dateFormatGmt.toString()).build();
+                    .header("Last-Modified", dateFormatGmt.format(new Date())).build();
         } else {
             resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).build();
         }
@@ -91,7 +105,6 @@ public class RestconfDataServiceImpl implements RestconfDataService {
 
     @Override
     public Response putData(final String identifier, final NormalizedNodeContext payload) {
-        Preconditions.checkNotNull(identifier);
         Preconditions.checkNotNull(payload);
 
         final InstanceIdentifierContext<? extends SchemaNode> iid = payload
@@ -102,18 +115,18 @@ public class RestconfDataServiceImpl implements RestconfDataService {
         PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
 
         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
-        DOMTransactionChain transaction = null;
-        SchemaContextRef ref = null;
+        final DOMTransactionChain transactionChain;
+        final SchemaContextRef ref;
         if (mountPoint == null) {
-            transaction = this.transactionChainHandler.get();
+            transactionChain = this.transactionChainHandler.get();
             ref = new SchemaContextRef(this.schemaContextHandler.get());
         } else {
-            transaction = transactionOfMountPoint(mountPoint);
+            transactionChain = transactionChainOfMountPoint(mountPoint);
             ref = new SchemaContextRef(mountPoint.getSchemaContext());
         }
 
         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
-                payload.getInstanceIdentifierContext(), mountPoint, transaction);
+                payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
         return PutDataTransactionUtil.putData(payload, ref, transactionNode);
     }
 
@@ -127,42 +140,41 @@ public class RestconfDataServiceImpl implements RestconfDataService {
         Preconditions.checkNotNull(payload);
 
         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
-        DOMTransactionChain transaction = null;
-        SchemaContextRef ref = null;
+        final DOMTransactionChain transactionChain;
+        final SchemaContextRef ref;
         if (mountPoint == null) {
-            transaction = this.transactionChainHandler.get();
+            transactionChain = this.transactionChainHandler.get();
             ref = new SchemaContextRef(this.schemaContextHandler.get());
         } else {
-            transaction = transactionOfMountPoint(mountPoint);
+            transactionChain = transactionChainOfMountPoint(mountPoint);
             ref = new SchemaContextRef(mountPoint.getSchemaContext());
         }
         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
-                payload.getInstanceIdentifierContext(), mountPoint, transaction);
+                payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
         return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref);
     }
 
     @Override
     public Response deleteData(final String identifier) {
         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
-        final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(identifier,
-                schemaContextRef.get());
+        final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
+                identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
 
         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
-        final DOMTransactionChain transaction;
+        final DOMTransactionChain transactionChain;
         if (mountPoint == null) {
-            transaction = this.transactionChainHandler.get();
+            transactionChain = this.transactionChainHandler.get();
         } else {
-            transaction = transactionOfMountPoint(mountPoint);
+            transactionChain = transactionChainOfMountPoint(mountPoint);
         }
 
         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
-                transaction);
+                transactionChain);
         return DeleteDataTransactionUtil.deleteData(transactionNode);
     }
 
     @Override
     public PATCHStatusContext patchData(final String identifier, final PATCHContext context, final UriInfo uriInfo) {
-        Preconditions.checkNotNull(identifier);
         return patchData(context, uriInfo);
     }
 
@@ -171,29 +183,29 @@ public class RestconfDataServiceImpl implements RestconfDataService {
         Preconditions.checkNotNull(context);
         final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint();
 
-        final DOMTransactionChain transaction;
+        final DOMTransactionChain transactionChain;
         final SchemaContextRef ref;
         if (mountPoint == null) {
-            transaction = this.transactionChainHandler.get();
+            transactionChain = this.transactionChainHandler.get();
             ref = new SchemaContextRef(this.schemaContextHandler.get());
         } else {
-            transaction = transactionOfMountPoint(mountPoint);
+            transactionChain = transactionChainOfMountPoint(mountPoint);
             ref = new SchemaContextRef(mountPoint.getSchemaContext());
         }
 
         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
-                context.getInstanceIdentifierContext(), mountPoint, transaction);
+                context.getInstanceIdentifierContext(), mountPoint, transactionChain);
 
         return PatchDataTransactionUtil.patchData(context, transactionNode, ref);
     }
 
     /**
-     * Prepare transaction to read data of mount point, if these data are
-     * present.
+     * Prepare transaction chain to access data of mount point
      * @param mountPoint
-     * @return {@link DOMDataReadWriteTransaction}
+     *            - mount point reference
+     * @return {@link DOMTransactionChain}
      */
-    private static DOMTransactionChain transactionOfMountPoint(final DOMMountPoint mountPoint) {
+    private static DOMTransactionChain transactionChainOfMountPoint(@Nonnull final DOMMountPoint mountPoint) {
         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
         if (domDataBrokerService.isPresent()) {
             return domDataBrokerService.get().createTransactionChain(RestConnectorProvider.transactionListener);
index 5f0d995b00c54b5970789d501cd5196b88589b34..fe840990ed1f4db44d5eb076c9e3dc9aea81170b 100644 (file)
@@ -8,10 +8,13 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.CheckedFuture;
 import java.util.Collection;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
@@ -19,23 +22,23 @@ import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 
 /**
@@ -62,24 +65,31 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    public static NormalizedNode<?, ?> readData(final String valueOfContent, final TransactionVarsWrapper transactionNode) {
+    public static @Nullable NormalizedNode<?, ?> readData(@Nullable final String valueOfContent,
+                                                          @Nonnull final TransactionVarsWrapper transactionNode) {
+        final NormalizedNode<?, ?> data;
         if (valueOfContent != null) {
             switch (valueOfContent) {
                 case RestconfDataServiceConstant.ReadData.CONFIG:
                     transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
-                    return readDataViaTransaction(transactionNode);
+                    data = readDataViaTransaction(transactionNode);
+                    break;
                 case RestconfDataServiceConstant.ReadData.NONCONFIG:
                     transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
-                    return readDataViaTransaction(transactionNode);
+                    data = readDataViaTransaction(transactionNode);
+                    break;
                 case RestconfDataServiceConstant.ReadData.ALL:
-                    return readDataViaTransaction(transactionNode);
+                    data = readAllData(transactionNode);
+                    break;
                 default:
-                    throw new RestconfDocumentedException("Bad querry parameter for content.", ErrorType.APPLICATION,
+                    throw new RestconfDocumentedException("Bad query parameter for content.", ErrorType.APPLICATION,
                             ErrorTag.INVALID_VALUE);
             }
         } else {
-            return readDataViaTransaction(transactionNode);
+            data = readAllData(transactionNode);
         }
+
+        return data;
     }
 
     /**
@@ -91,18 +101,15 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> readDataViaTransaction(final TransactionVarsWrapper transactionNode) {
-        if (transactionNode.getLogicalDatastoreType() != null) {
-            final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = transactionNode
-                    .getTransactionChain().newReadOnlyTransaction().read(transactionNode.getLogicalDatastoreType(),
-                            transactionNode.getInstanceIdentifier().getInstanceIdentifier());
-            final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
-            FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
-                    dataFactory);
-            return dataFactory.build();
-        } else {
-            return readAllData(transactionNode);
-        }
+    private static @Nullable NormalizedNode<?, ?> readDataViaTransaction(
+            @Nonnull final TransactionVarsWrapper transactionNode) {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> listenableFuture = transactionNode
+                .getTransactionChain().newReadOnlyTransaction().read(transactionNode.getLogicalDatastoreType(),
+                        transactionNode.getInstanceIdentifier().getInstanceIdentifier());
+        final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
+        FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+                dataFactory);
+        return dataFactory.build();
     }
 
     /**
@@ -112,7 +119,7 @@ public final class ReadDataTransactionUtil {
      *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> readAllData(final TransactionVarsWrapper transactionNode) {
+    private static @Nullable NormalizedNode<?, ?> readAllData(@Nonnull final TransactionVarsWrapper transactionNode) {
         // PREPARE STATE DATA NODE
         transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
         final NormalizedNode<?, ?> stateDataNode = readDataViaTransaction(transactionNode);
@@ -123,10 +130,7 @@ public final class ReadDataTransactionUtil {
 
         // if no data exists
         if ((stateDataNode == null) && (configDataNode == null)) {
-            throw new RestconfDocumentedException(
-                    "Request could not be completed because the relevant data model content does not exist",
-                    ErrorType.PROTOCOL,
-                    ErrorTag.DATA_MISSING);
+            return null;
         }
 
         // return config data
@@ -150,12 +154,10 @@ public final class ReadDataTransactionUtil {
      *            - data node of state data
      * @param configDataNode
      *            - data node of config data
-     * @param transactionNode
-     *            - {@link TransactionVarsWrapper} - wrapper for variables
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> mapNode(final NormalizedNode<?, ?> stateDataNode,
-            final NormalizedNode<?, ?> configDataNode) {
+    private static @Nonnull NormalizedNode<?, ?> mapNode(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+                                                         @Nonnull final NormalizedNode<?, ?> configDataNode) {
         validPossibilityOfMergeNodes(stateDataNode, configDataNode);
         if (configDataNode instanceof RpcDefinition) {
             return prepareRpcData(configDataNode, stateDataNode);
@@ -164,6 +166,23 @@ public final class ReadDataTransactionUtil {
         }
     }
 
+    /**
+     * Valid of can be data merged together.
+     *
+     * @param stateDataNode
+     *            - data node of state data
+     * @param configDataNode
+     *            - data node of config data
+     */
+    private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
+                                                     @Nonnull final NormalizedNode<?, ?> configDataNode) {
+        final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
+        final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
+        if (moduleOfStateData != moduleOfConfigData) {
+            throw new RestconfDocumentedException("It is not possible to merge ");
+        }
+    }
+
     /**
      * Prepare and map data for rpc
      *
@@ -173,8 +192,8 @@ public final class ReadDataTransactionUtil {
      *            - data node of state data
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> prepareRpcData(final NormalizedNode<?, ?> configDataNode,
-            final NormalizedNode<?, ?> stateDataNode) {
+    private static @Nonnull NormalizedNode<?, ?> prepareRpcData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+                                                                @Nonnull final NormalizedNode<?, ?> stateDataNode) {
         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
                 .mapEntryBuilder();
         mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
@@ -195,11 +214,10 @@ public final class ReadDataTransactionUtil {
      * @param mapEntryBuilder
      *            - builder for mapping data
      */
-    private static void mapRpcDataNode(final NormalizedNode<?, ?> dataNode,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : ((ContainerNode) dataNode).getValue()) {
-            mapEntryBuilder.addChild(child);
-        }
+    private static void mapRpcDataNode(@Nonnull final NormalizedNode<?, ?> dataNode,
+                                       @Nonnull final DataContainerNodeBuilder<
+                                               NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
+        ((ContainerNode) dataNode).getValue().forEach(mapEntryBuilder::addChild);
     }
 
     /**
@@ -211,121 +229,120 @@ public final class ReadDataTransactionUtil {
      *            - data node of state data
      * @return {@link NormalizedNode}
      */
-    private static NormalizedNode<?, ?> prepareData(final NormalizedNode<?, ?> configDataNode,
-            final NormalizedNode<?, ?> stateDataNode) {
-
-        if (configDataNode instanceof MapNode) { // part for lists mapping
-            final MapNode immutableStateData = ImmutableNodes.mapNodeBuilder(stateDataNode.getNodeType())
-                .addChild((MapEntryNode) stateDataNode).build();
-            final MapNode immutableConfigData = ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType())
-                .addChild((MapEntryNode) configDataNode).build();
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder = ImmutableNodes
-                .mapEntryBuilder();
-            mapEntryBuilder.withNodeIdentifier((NodeIdentifierWithPredicates) configDataNode.getIdentifier());
-
-            // MAP CONFIG DATA
-            mapDataNode(immutableConfigData, mapEntryBuilder);
-            // MAP STATE DATA
-            mapDataNode(immutableStateData, mapEntryBuilder);
-            return ImmutableNodes.mapNodeBuilder(configDataNode.getNodeType()).addChild(mapEntryBuilder.build()).build();
-        } else if (configDataNode instanceof ContainerNode) { // part for
-                                                              // containers
-                                                              // mapping
-            final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder = Builders
-                    .containerBuilder((ContainerNode) configDataNode);
-            // MAP CONFIG DATA
-            mapCont(containerBuilder, ((ContainerNode) configDataNode).getValue());
-            // MAP STATE DATA
-            mapCont(containerBuilder, ((ContainerNode) stateDataNode).getValue());
-
-            return containerBuilder.build();
+    private static @Nonnull NormalizedNode<?, ?> prepareData(@Nonnull final NormalizedNode<?, ?> configDataNode,
+                                                             @Nonnull final NormalizedNode<?, ?> stateDataNode) {
+        if (configDataNode instanceof MapNode) {
+            final CollectionNodeBuilder<MapEntryNode, MapNode> builder = ImmutableNodes
+                    .mapNodeBuilder().withNodeIdentifier(((MapNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((MapNode) configDataNode).getValue(), ((MapNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof MapEntryNode) {
+            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> builder = ImmutableNodes
+                    .mapEntryBuilder().withNodeIdentifier(((MapEntryNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((MapEntryNode) configDataNode).getValue(), ((MapEntryNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof ContainerNode) {
+            final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = Builders
+                    .containerBuilder().withNodeIdentifier(((ContainerNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((ContainerNode) configDataNode).getValue(), ((ContainerNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof AugmentationNode) {
+            final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> builder = Builders
+                    .augmentationBuilder().withNodeIdentifier(((AugmentationNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((AugmentationNode) configDataNode).getValue(), ((AugmentationNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof ChoiceNode) {
+            final DataContainerNodeBuilder<NodeIdentifier, ChoiceNode> builder = Builders
+                    .choiceBuilder().withNodeIdentifier(((ChoiceNode) configDataNode).getIdentifier());
+
+            mapValueToBuilder(
+                    ((ChoiceNode) configDataNode).getValue(), ((ChoiceNode) stateDataNode).getValue(), builder);
+
+            return builder.build();
+        } else if (configDataNode instanceof LeafNode) {
+            return ImmutableNodes.leafNode(configDataNode.getNodeType(), configDataNode.getValue());
         } else {
             throw new RestconfDocumentedException("Bad type of node.");
         }
     }
 
     /**
-     * Map data to builder
+     * Map value from container node to builder.
      *
-     * @param containerBuilder
-     *            - builder for mapping data
-     * @param childs
-     *            - childs of data (container)
+     * @param configData
+     *            - collection of config data nodes
+     * @param stateData
+     *            - collection of state data nodes
+     * @param builder
+     *            - builder
      */
-    private static void mapCont(final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> containerBuilder,
-            final Collection<DataContainerChild<? extends PathArgument, ?>> childs) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : childs) {
-            containerBuilder.addChild(child);
-        }
-    }
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapValueToBuilder(
+            @Nonnull final Collection<T> configData,
+            @Nonnull final Collection<T> stateData,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        final Map<PathArgument, T> configMap = configData.stream().collect(
+                Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
+        final Map<PathArgument, T> stateMap = stateData.stream().collect(
+                Collectors.toMap(NormalizedNode::getIdentifier, Function.identity()));
 
-    /**
-     * Map data to builder
-     *
-     * @param immutableData
-     *            - immutable data - {@link MapNode}
-     * @param mapEntryBuilder
-     *            - builder for mapping data
-     */
-    private static void mapDataNode(final MapNode immutableData,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        for (final DataContainerChild<? extends PathArgument, ?> child : immutableData.getValue().iterator()
-                .next().getValue()) {
-            Preconditions.checkNotNull(child);
-            if (child instanceof ContainerNode) {
-                addChildToMap(ContainerNode.class, child, mapEntryBuilder);
-            } else if (child instanceof AugmentationNode) {
-                addChildToMap(AugmentationNode.class, child, mapEntryBuilder);
-            } else if(child instanceof MapNode){
-                final MapNode listNode = (MapNode) child;
-                for (final MapEntryNode listChild : listNode.getValue()) {
-                    for (final DataContainerChild<? extends PathArgument, ?> entryChild : listChild.getValue()) {
-                        addChildToMap(MapEntryNode.class, entryChild, mapEntryBuilder);
-                    }
-                }
-            } else if (child instanceof ChoiceNode) {
-                addChildToMap(ChoiceNode.class, child, mapEntryBuilder);
-            } else if ((child instanceof LeafSetNode<?>) || (child instanceof LeafNode)) {
-                mapEntryBuilder.addChild(child);
-            }
+        // merge config and state data of children with different identifiers
+        mapDataToBuilder(configMap, stateMap, builder);
 
-        }
+        // merge config and state data of children with the same identifiers
+        mergeDataToBuilder(configMap, stateMap, builder);
     }
 
     /**
-     * Mapping child
+     * Map data with different identifiers to builder. Data with different identifiers can be just added
+     * as childs to parent node.
      *
-     * @param type
-     *            - type of data
-     * @param child
-     *            - child to map
-     * @param mapEntryBuilder
-     *            - builder for mapping child
+     * @param configMap
+     *            - map of config data nodes
+     * @param stateMap
+     *            - map of state data nodes
+     * @param builder
+     *           - builder
      */
-    private static <T extends DataContainerNode<? extends PathArgument>> void addChildToMap(final Class<T> type,
-            final DataContainerChild<? extends PathArgument, ?> child,
-            final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder) {
-        @SuppressWarnings("unchecked")
-        final T node = (T) child;
-        for (final DataContainerChild<? extends PathArgument, ?> childNode : node.getValue()) {
-            mapEntryBuilder.addChild(childNode);
-        }
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mapDataToBuilder(
+            @Nonnull final Map<PathArgument, T> configMap,
+            @Nonnull final Map<PathArgument, T> stateMap,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        configMap.entrySet().stream().filter(x -> !stateMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild(y.getValue()));
+        stateMap.entrySet().stream().filter(x -> !configMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild(y.getValue()));
     }
 
     /**
-     * Valid of can be data merged together.
+     * Map data with the same identifiers to builder. Data with the same identifiers cannot be just added but we need to
+     * go one level down with {@code prepareData} method.
      *
-     * @param stateDataNode
-     *            - data node of state data
-     * @param configDataNode
-     *            - data node of config data
+     * @param configMap
+     *            - immutable config data
+     * @param stateMap
+     *            - immutable state data
+     * @param builder
+     *           - builder
      */
-    private static void validPossibilityOfMergeNodes(@Nonnull final NormalizedNode<?, ?> stateDataNode,
-            @Nonnull final NormalizedNode<?, ?> configDataNode) {
-        final QNameModule moduleOfStateData = stateDataNode.getIdentifier().getNodeType().getModule();
-        final QNameModule moduleOfConfigData = configDataNode.getIdentifier().getNodeType().getModule();
-        if (moduleOfStateData != moduleOfConfigData) {
-            throw new RestconfDocumentedException("It is not possible to merge ");
-        }
+    @SuppressWarnings("unchecked")
+    private static <T extends NormalizedNode<? extends PathArgument, ?>> void mergeDataToBuilder(
+            @Nonnull final Map<PathArgument, T> configMap,
+            @Nonnull final Map<PathArgument, T> stateMap,
+            @Nonnull final NormalizedNodeContainerBuilder<?, PathArgument, T, ?> builder) {
+        // it is enough to process only config data because operational contains the same data
+        configMap.entrySet().stream().filter(x -> stateMap.containsKey(x.getKey())).forEach(
+                y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
     }
 }
index d24c3deb07336c99016c453b3810d1cf44f556d9..fc2703f98be3a9eb3a2a4496e2554f0a9e05919d 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.restconf.utils.parser;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -14,7 +15,6 @@ import java.text.ParseException;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
-import javax.annotation.Nullable;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
 import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
@@ -48,25 +48,62 @@ public final class ParserIdentifier {
     }
 
     /**
-     * Make {@link InstanceIdentifierContext} from identifier.
+     * Make {@link InstanceIdentifierContext} from {@link String} identifier
+     * <br>
+     * For identifiers of data NOT behind mount points returned
+     * {@link InstanceIdentifierContext} is prepared with {@code null} reference of {@link DOMMountPoint} and with
+     * controller's {@link SchemaContext}.
+     * <br>
+     * For identifiers of data behind mount points returned
+     * {@link InstanceIdentifierContext} is prepared with reference of {@link DOMMountPoint} and its
+     * own {@link SchemaContext}.
      *
      * @param identifier
-     *            - path identifier
+     *           - path identifier
      * @param schemaContext
-     *            - {@link SchemaContext}
+     *           - controller schema context
+     * @param mountPointService
+     *           - mount point service
      * @return {@link InstanceIdentifierContext}
      */
-    public static InstanceIdentifierContext<?> toInstanceIdentifier(@Nullable final String identifier,
-            final SchemaContext schemaContext) {
-        final YangInstanceIdentifier deserialize;
+    public static InstanceIdentifierContext<?> toInstanceIdentifier(
+            final String identifier,
+            final SchemaContext schemaContext,
+            final Optional<DOMMountPointService> mountPointService) {
         if (identifier != null && identifier.contains(RestconfConstants.MOUNT)) {
-            final String mountPointId = identifier.substring(0, identifier.indexOf("/" + RestconfConstants.MOUNT));
-            deserialize = IdentifierCodec.deserialize(mountPointId, schemaContext);
+            if (!mountPointService.isPresent()) {
+                throw new RestconfDocumentedException("Mount point service is not available");
+            }
+
+            final Iterator<String> pathsIt = Splitter.on("/" + RestconfConstants.MOUNT).split(identifier).iterator();
+
+            final String mountPointId = pathsIt.next();
+            final YangInstanceIdentifier mountYangInstanceIdentifier = IdentifierCodec.deserialize(
+                    mountPointId, schemaContext);
+            final Optional<DOMMountPoint> mountPoint = mountPointService.get().getMountPoint(mountYangInstanceIdentifier);
+
+            if (!mountPoint.isPresent()) {
+                throw new RestconfDocumentedException(
+                        "Mount point does not exist.", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+            }
+
+            final String pathId = pathsIt.next().replaceFirst("/", "");
+            final YangInstanceIdentifier pathYangInstanceIdentifier = IdentifierCodec.deserialize(
+                    pathId, mountPoint.get().getSchemaContext());
+
+            final DataSchemaContextNode<?> child = DataSchemaContextTree.from(
+                    mountPoint.get().getSchemaContext()).getChild(pathYangInstanceIdentifier);
+
+            return new InstanceIdentifierContext<SchemaNode>(
+                    pathYangInstanceIdentifier, child.getDataSchemaNode(), mountPoint.get(),
+                    mountPoint.get().getSchemaContext());
         } else {
-            deserialize = IdentifierCodec.deserialize(identifier, schemaContext);
+            final YangInstanceIdentifier deserialize = IdentifierCodec.deserialize(identifier, schemaContext);
+            final DataSchemaContextNode<?> child = DataSchemaContextTree.from(schemaContext).getChild(deserialize);
+
+            return new InstanceIdentifierContext<SchemaNode>(
+                    deserialize, child.getDataSchemaNode(), null, schemaContext);
         }
-        final DataSchemaContextNode<?> child = DataSchemaContextTree.from(schemaContext).getChild(deserialize);
-        return new InstanceIdentifierContext<SchemaNode>(deserialize, child.getDataSchemaNode(), null, schemaContext);
     }
 
     /**
@@ -170,12 +207,11 @@ public final class ParserIdentifier {
                 pathBuilder.append(current);
             }
             final InstanceIdentifierContext<?> point = ParserIdentifier
-                    .toInstanceIdentifier(pathBuilder.toString(), schemaContext);
-            final DOMMountPoint mountPoint = domMountPointService.getMountPoint(point.getInstanceIdentifier()).get();
+                    .toInstanceIdentifier(pathBuilder.toString(), schemaContext, Optional.of(domMountPointService));
             final String moduleName = RestconfValidation.validateAndGetModulName(componentIter);
             final Date revision = RestconfValidation.validateAndGetRevision(componentIter);
-            final Module module = mountPoint.getSchemaContext().findModuleByName(moduleName, revision);
-            return new SchemaExportContext(mountPoint.getSchemaContext(), module);
+            final Module module = point.getMountPoint().getSchemaContext().findModuleByName(moduleName, revision);
+            return new SchemaExportContext(point.getMountPoint().getSchemaContext(), module);
         }
     }
 }
index 998c99c3c53c1b5d32be8a274f8e4bf5f8251b9f..523625ad82b3329db21152591adf08ac5f2749f0 100644 (file)
@@ -556,7 +556,8 @@ public class RestconfModulesServiceTest {
     /**
      * Negative test of getting specific module supported by the mount point when specified mount point is not found
      * (it is not registered in <code>DOMMountPointService</code>). Test is expected to fail with
-     * <code>IllegalStateException</code>.
+     * <code>RestconfDocumentedException</code> and error type, error tag and error status code are compared to
+     * expected values.
      */
     @Test
     public void getModuleMountPointNotFoundNegativeTest() throws Exception {
@@ -564,14 +565,21 @@ public class RestconfModulesServiceTest {
         final RestconfModulesService modulesService = setupNormalMountPoint();
 
         // make test
-        this.thrown.expect(IllegalStateException.class);
-        modulesService.getModule(TEST_MODULE_BEHIND_NOT_REGISTERED_MOUNT_POINT, null);
+        try {
+            modulesService.getModule(TEST_MODULE_BEHIND_NOT_REGISTERED_MOUNT_POINT, null);
+            fail("Test should fail due to missing mount point");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
     }
 
     /**
      * Negative test of getting all modules supported by the mount point when specified mount point is not found (it
      * is not registered in <code>DOMMountPointService</code>). Test is expected to fail with
-     * <code>IllegalStateException</code>.
+     * <code>RestconfDocumentedException</code> and error type, error tag and error status code are compared to
+     * expected values.
      */
     @Test
     public void getModulesMountPointNotFoundNegativeTest() throws Exception {
@@ -579,7 +587,13 @@ public class RestconfModulesServiceTest {
         final RestconfModulesService modulesService = setupNormalMountPoint();
 
         // make test
-        this.thrown.expect(IllegalStateException.class);
-        modulesService.getModules(NOT_REGISTERED_MOUNT_POINT, null);
+        try {
+            modulesService.getModules(NOT_REGISTERED_MOUNT_POINT, null);
+            fail("Test should fail due to missing mount point");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct", ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error code is not correct", 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
     }
 }
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImplTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImplTest.java
new file mode 100644 (file)
index 0000000..51f5c89
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.restful.services.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.restconf.RestConnectorProvider;
+import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
+import org.opendaylight.restconf.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.handlers.TransactionChainHandler;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+public class RestconfDataServiceImplTest {
+
+    private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
+
+    private ContainerNode buildBaseCont;
+    private ContainerNode buildBaseContConfig;
+    private ContainerNode buildBaseContOperational;
+    private SchemaContextRef contextRef;
+    private YangInstanceIdentifier iidBase;
+    private DataSchemaNode schemaNode;
+    private RestconfDataServiceImpl dataService;
+    private QName baseQName;
+    private QName containerPlayerQname;
+    private QName leafQname;
+    private ContainerNode buildPlayerCont;
+    private ContainerNode buildLibraryCont;
+    private MapNode buildPlaylistList;
+
+    @Mock
+    private TransactionChainHandler transactionChainHandler;
+    @Mock
+    private DOMTransactionChain domTransactionChain;
+    @Mock
+    private UriInfo uriInfo;
+    @Mock
+    private DOMDataReadWriteTransaction readWrite;
+    @Mock
+    private DOMDataReadOnlyTransaction read;
+    @Mock
+    private DOMDataWriteTransaction write;
+    @Mock
+    private DOMMountPointServiceHandler mountPointServiceHandler;
+    @Mock
+    private DOMMountPointService mountPointService;
+    @Mock
+    private DOMMountPoint mountPoint;
+    @Mock
+    private DOMDataBroker mountDataBroker;
+    @Mock
+    private DOMTransactionChain transactionChain;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        this.baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
+        this.containerPlayerQname = QName.create(this.baseQName, "player");
+        this.leafQname = QName.create(this.baseQName, "gap");
+
+        final QName containerLibraryQName = QName.create(this.baseQName, "library");
+        final QName listPlaylistQName = QName.create(this.baseQName, "playlist");
+
+        final LeafNode buildLeaf = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(this.leafQname))
+                .withValue(0.2)
+                .build();
+
+        this.buildPlayerCont = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(this.containerPlayerQname))
+                .withChild(buildLeaf)
+                .build();
+
+        this.buildLibraryCont = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(containerLibraryQName))
+                .build();
+
+        this.buildPlaylistList = Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listPlaylistQName))
+                .build();
+
+        this.buildBaseCont = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(this.baseQName))
+                .withChild(this.buildPlayerCont)
+                .build();
+
+        // config contains one child the same as in operational and one additional
+        this.buildBaseContConfig = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(this.baseQName))
+                .withChild(this.buildPlayerCont)
+                .withChild(this.buildLibraryCont)
+                .build();
+
+        // operational contains one child the same as in config and one additional
+        this.buildBaseContOperational = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(this.baseQName))
+                .withChild(this.buildPlayerCont)
+                .withChild(this.buildPlaylistList)
+                .build();
+
+        this.iidBase = YangInstanceIdentifier.builder()
+                .node(this.baseQName)
+                .build();
+
+        this.contextRef = new SchemaContextRef(TestRestconfUtils.loadSchemaContext(PATH_FOR_NEW_SCHEMA_CONTEXT));
+        this.schemaNode = DataSchemaContextTree.from(this.contextRef.get()).getChild(this.iidBase).getDataSchemaNode();
+
+        final SchemaContextHandler schemaContextHandler = new SchemaContextHandler();
+
+        schemaContextHandler.onGlobalContextUpdated(this.contextRef.get());
+        this.dataService = new RestconfDataServiceImpl(schemaContextHandler, this.transactionChainHandler, this.mountPointServiceHandler);
+        doReturn(this.domTransactionChain).when(this.transactionChainHandler).get();
+        doReturn(this.read).when(this.domTransactionChain).newReadOnlyTransaction();
+        doReturn(this.readWrite).when(this.domTransactionChain).newReadWriteTransaction();
+        doReturn(this.write).when(this.domTransactionChain).newWriteOnlyTransaction();
+        doReturn(this.mountPointService).when(this.mountPointServiceHandler).get();
+        doReturn(Optional.of(this.mountPoint)).when(this.mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
+        doReturn(this.contextRef.get()).when(this.mountPoint).getSchemaContext();
+        doReturn(Optional.of(this.mountDataBroker)).when(this.mountPoint).getService(DOMDataBroker.class);
+        doReturn(this.transactionChain).when(this.mountDataBroker).createTransactionChain(any(TransactionChainListener.class));
+        doReturn(this.read).when(this.transactionChain).newReadOnlyTransaction();
+        doReturn(this.readWrite).when(this.transactionChain).newReadWriteTransaction();
+    }
+
+    @Test
+    public void testReadData() {
+        doReturn(new MultivaluedHashMap<String, String>()).when(this.uriInfo).getQueryParameters();
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseCont))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(this.read).read(LogicalDatastoreType.OPERATIONAL, this.iidBase);
+        final Response response = this.dataService.readData("example-jukebox:jukebox", this.uriInfo);
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+        assertEquals(this.buildBaseCont, ((NormalizedNodeContext) response.getEntity()).getData());
+    }
+
+    /**
+     * Test read data from mount point when both {@link LogicalDatastoreType#CONFIGURATION} and
+     * {@link LogicalDatastoreType#OPERATIONAL} contains the same data and some additional data to be merged.
+     */
+    @Test
+    public void testReadDataMountPoint() {
+        doReturn(new MultivaluedHashMap<String, String>()).when(this.uriInfo).getQueryParameters();
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseContConfig))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseContOperational))).when(this.read)
+                .read(LogicalDatastoreType.OPERATIONAL, this.iidBase);
+
+        final Response response = this.dataService.readData(
+                "example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", this.uriInfo);
+
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+
+        // response must contain all child nodes from config and operational containers merged in one container
+        final NormalizedNode<?, ?> data = ((NormalizedNodeContext) response.getEntity()).getData();
+        assertTrue(data instanceof ContainerNode);
+        assertEquals(3, ((ContainerNode) data).getValue().size());
+        assertTrue(((ContainerNode) data).getChild(this.buildPlayerCont.getIdentifier()).isPresent());
+        assertTrue(((ContainerNode) data).getChild(this.buildLibraryCont.getIdentifier()).isPresent());
+        assertTrue(((ContainerNode) data).getChild(this.buildPlaylistList.getIdentifier()).isPresent());
+    }
+
+    @Test(expected = RestconfDocumentedException.class)
+    public void testReadDataNoData() {
+        doReturn(new MultivaluedHashMap<String, String>()).when(this.uriInfo).getQueryParameters();
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(this.read).read(LogicalDatastoreType.CONFIGURATION,
+                this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(this.read).read(LogicalDatastoreType.OPERATIONAL,
+                this.iidBase);
+        this.dataService.readData("example-jukebox:jukebox", this.uriInfo);
+    }
+
+    @Ignore
+    @Test
+    public void testPutData() {
+        final InstanceIdentifierContext<DataSchemaNode> iidContext = new InstanceIdentifierContext<>(this.iidBase, this.schemaNode, null, this.contextRef.get());
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseCont);
+
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseCont))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doNothing().when(this.write).put(LogicalDatastoreType.CONFIGURATION, this.iidBase, payload.getData());
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.write).submit();
+        final Response response = this.dataService.putData(null, payload);
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+    }
+
+    @Ignore
+    @Test
+    public void testPutDataWithMountPoint() {
+        final DOMDataBroker dataBroker = Mockito.mock(DOMDataBroker.class);
+        final DOMMountPoint mountPoint = Mockito.mock(DOMMountPoint.class);
+        doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+        doReturn(this.transactionChainHandler.get()).when(dataBroker).createTransactionChain(RestConnectorProvider.transactionListener);
+        final InstanceIdentifierContext<DataSchemaNode> iidContext = new InstanceIdentifierContext<>(this.iidBase, this.schemaNode, mountPoint, this.contextRef.get());
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.buildBaseCont);
+
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseCont))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doNothing().when(this.write).put(LogicalDatastoreType.CONFIGURATION, this.iidBase, payload.getData());
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.write).submit();
+        final Response response = this.dataService.putData(null, payload);
+        assertNotNull(response);
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testPostData() {
+        final QName listQname = QName.create(this.baseQName, "playlist");
+        final QName listKeyQname = QName.create(this.baseQName, "name");
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeWithKey =
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(listQname, listKeyQname, "name of band");
+        final LeafNode<Object> content = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(this.baseQName, "name")))
+                .withValue("name of band")
+                .build();
+        final LeafNode<Object> content2 = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(this.baseQName, "description")))
+                .withValue("band description")
+                .build();
+        final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
+                .withNodeIdentifier(nodeWithKey)
+                .withChild(content)
+                .withChild(content2)
+                .build();
+        final MapNode buildList = Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listQname))
+                .withChild(mapEntryNode)
+                .build();
+
+        doReturn(new MultivaluedHashMap<String, String>()).when(this.uriInfo).getQueryParameters();
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(this.iidBase, null, null, this.contextRef.get());
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, buildList);
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(this.read).read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        final MapNode data = (MapNode) payload.getData();
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates identifier = data.getValue().iterator().next().getIdentifier();
+        final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier);
+        doReturn(Futures.immediateCheckedFuture(false)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData());
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.readWrite).submit();
+        doReturn(UriBuilder.fromUri("http://localhost:8181/restconf/15/")).when(this.uriInfo).getBaseUriBuilder();
+
+        final Response response = this.dataService.postData(null, payload, this.uriInfo);
+        assertEquals(201, response.getStatus());
+    }
+
+    @Test
+    public void testDeleteData() {
+        doNothing().when(this.readWrite).delete(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.readWrite).submit();
+        doReturn(Futures.immediateCheckedFuture(true)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        final Response response = this.dataService.deleteData("example-jukebox:jukebox");
+        assertNotNull(response);
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    /**
+     * Test of deleting data on mount point
+     */
+    @Test
+    public void testDeleteDataMountPoint() {
+        doNothing().when(this.readWrite).delete(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.readWrite).submit();
+        doReturn(Futures.immediateCheckedFuture(true)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        final Response response = this.dataService.deleteData("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox");
+        assertNotNull(response);
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    @Test
+    public void testPatchData() throws Exception {
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(this.iidBase, this.schemaNode, null, this.contextRef.get());
+        final List<PATCHEntity> entity = new ArrayList<>();
+        final YangInstanceIdentifier iidleaf = YangInstanceIdentifier.builder(this.iidBase)
+                .node(this.containerPlayerQname)
+                .node(this.leafQname)
+                .build();
+        entity.add(new PATCHEntity("create data", "CREATE", this.iidBase, this.buildBaseCont));
+        entity.add(new PATCHEntity("replace data", "REPLACE", this.iidBase, this.buildBaseCont));
+        entity.add(new PATCHEntity("delete data", "DELETE", iidleaf));
+        final PATCHContext patch = new PATCHContext(iidContext, entity, "test patch id");
+
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseCont))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doNothing().when(this.write).put(LogicalDatastoreType.CONFIGURATION, this.iidBase, this.buildBaseCont);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.write).submit();
+        doNothing().when(this.readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.readWrite).submit();
+        doReturn(Futures.immediateCheckedFuture(false)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(true)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        final PATCHStatusContext status = this.dataService.patchData(patch, this.uriInfo);
+        assertTrue(status.isOk());
+        assertEquals(3, status.getEditCollection().size());
+        assertEquals("replace data", status.getEditCollection().get(1).getEditId());
+    }
+
+    @Test
+    public void testPatchDataDeleteNotExist() throws Exception {
+        final Field handler = RestConnectorProvider.class.getDeclaredField("transactionChainHandler");
+        final Field broker = RestConnectorProvider.class.getDeclaredField("dataBroker");
+
+        handler.setAccessible(true);
+        handler.set(RestConnectorProvider.class, mock(TransactionChainHandler.class));
+
+        broker.setAccessible(true);
+        broker.set(RestConnectorProvider.class, mock(DOMDataBroker.class));
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(this.iidBase, this.schemaNode, null, this.contextRef.get());
+        final List<PATCHEntity> entity = new ArrayList<>();
+        final YangInstanceIdentifier iidleaf = YangInstanceIdentifier.builder(this.iidBase)
+                .node(this.containerPlayerQname)
+                .node(this.leafQname)
+                .build();
+        entity.add(new PATCHEntity("create data", "CREATE", this.iidBase, this.buildBaseCont));
+        entity.add(new PATCHEntity("remove data", "REMOVE", iidleaf));
+        entity.add(new PATCHEntity("delete data", "DELETE", iidleaf));
+        final PATCHContext patch = new PATCHContext(iidContext, entity, "test patch id");
+
+        doReturn(Futures.immediateCheckedFuture(Optional.of(this.buildBaseCont))).when(this.read)
+                .read(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doNothing().when(this.write).put(LogicalDatastoreType.CONFIGURATION, this.iidBase, this.buildBaseCont);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.write).submit();
+        doNothing().when(this.readWrite).delete(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        doReturn(Futures.immediateCheckedFuture(null)).when(this.readWrite).submit();
+        doReturn(Futures.immediateCheckedFuture(false)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidBase);
+        doReturn(Futures.immediateCheckedFuture(false)).when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, iidleaf);
+        doReturn(true).when(this.readWrite).cancel();
+        final PATCHStatusContext status = this.dataService.patchData(patch, this.uriInfo);
+
+        handler.set(RestConnectorProvider.class, null);
+        handler.setAccessible(false);
+
+        broker.set(RestConnectorProvider.class, null);
+        broker.setAccessible(false);
+
+        assertFalse(status.isOk());
+        assertEquals(3, status.getEditCollection().size());
+        assertTrue(status.getEditCollection().get(0).isOk());
+        assertTrue(status.getEditCollection().get(1).isOk());
+        assertFalse(status.getEditCollection().get(2).isOk());
+        assertFalse(status.getEditCollection().get(2).getEditErrors().isEmpty());
+        final String errorMessage = status.getEditCollection().get(2).getEditErrors().get(0).getErrorMessage();
+        assertEquals("Data does not exist", errorMessage);
+    }
+}
\ No newline at end of file
index a5380a3004a55b27d54d132efbe12807e348ed00..368456a97c486c54ae0b2cc2a1af4b89e3ef71f3 100644 (file)
@@ -32,6 +32,8 @@ import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.restconf.utils.RestconfConstants;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
@@ -43,13 +45,10 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
  * Unit tests for {@link ParserIdentifier}
  */
 public class ParserIdentifierTest {
-    // mount point identifier + expected result
+    // mount point identifier
     private static final String MOUNT_POINT_IDENT =
             "mount-point:mount-container/point-number" + "/" + RestconfConstants.MOUNT;
 
-    private static final String MOUNT_POINT_IDENT_RESULT =
-            "/(mount:point?revision=2016-06-02)mount-container/point-number";
-
     // invalid mount point identifier
     private static final String INVALID_MOUNT_POINT_IDENT =
             "mount-point:point-number" + "/" + RestconfConstants.MOUNT;
@@ -79,6 +78,8 @@ public class ParserIdentifierTest {
 
     // schema context with test modules
     private SchemaContext schemaContext;
+    // contains the same modules but it is different object (it can be compared with equals)
+    private SchemaContext schemaContextOnMountPoint;
 
     private static final String TEST_MODULE_NAME = "test-module";
     private static final String TEST_MODULE_REVISION = "2016-06-02";
@@ -89,8 +90,10 @@ public class ParserIdentifierTest {
     private DOMMountPointService mountPointService;
 
     // mock mount point and mount point service
-    @Mock DOMMountPoint mockMountPoint;
-    @Mock DOMMountPointService mockMountPointService;
+    @Mock
+    private DOMMountPoint mockMountPoint;
+    @Mock
+    private DOMMountPointService mockMountPointService;
 
     @Rule
     public final ExpectedException thrown = ExpectedException.none();
@@ -99,6 +102,7 @@ public class ParserIdentifierTest {
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         schemaContext = TestRestconfUtils.loadSchemaContext("/parser-identifier");
+        schemaContextOnMountPoint = TestRestconfUtils.loadSchemaContext("/parser-identifier");
 
         // create and register mount point
         mountPoint = SimpleDOMMountPoint.create(
@@ -107,7 +111,7 @@ public class ParserIdentifierTest {
                         .node(QName.create("mount:point", "2016-06-02", "point-number"))
                         .build(),
                 ImmutableClassToInstanceMap.copyOf(Maps.newHashMap()),
-                schemaContext
+                schemaContextOnMountPoint
         );
 
         mountPointService = new DOMMountPointServiceImpl();
@@ -129,7 +133,7 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierTest() {
         final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
-                TEST_IDENT, schemaContext);
+                TEST_IDENT, schemaContext, Optional.absent());
 
         assertEquals("Returned not expected identifier",
                 TEST_IDENT_RESULT, context .getInstanceIdentifier().toString());
@@ -142,7 +146,7 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierOtherModulesTest() {
         final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
-                TEST_IDENT_OTHERS, schemaContext);
+                TEST_IDENT_OTHERS, schemaContext, Optional.absent());
 
         assertEquals("Returned not expected identifier",
                 TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
@@ -155,10 +159,16 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierMountPointTest() {
         final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
-                MOUNT_POINT_IDENT, schemaContext);
+                MOUNT_POINT_IDENT + "/" + TEST_IDENT, schemaContext, Optional.of(mountPointService));
 
         assertEquals("Returned not expected identifier",
-                MOUNT_POINT_IDENT_RESULT, context.getInstanceIdentifier().toString());
+                TEST_IDENT_RESULT.toString(), context.getInstanceIdentifier().toString());
+
+        assertEquals("Mount point not found",
+                mountPoint, context.getMountPoint());
+
+        assertEquals("Schema context from mount point expected",
+                schemaContextOnMountPoint, context.getSchemaContext());
     }
 
     /**
@@ -167,7 +177,8 @@ public class ParserIdentifierTest {
      */
     @Test
     public void toInstanceIdentifierNullIdentifierTest() {
-        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                null, schemaContext, Optional.absent());
         assertEquals("Returned not expected identifier",
                 YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
     }
@@ -179,7 +190,7 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierNullSchemaContextNegativeTest() {
         thrown.expect(NullPointerException.class);
-        ParserIdentifier.toInstanceIdentifier(TEST_IDENT, null);
+        ParserIdentifier.toInstanceIdentifier(TEST_IDENT, null, Optional.absent());
     }
 
     /**
@@ -187,19 +198,8 @@ public class ParserIdentifierTest {
      */
     @Test
     public void toInstanceIdentifierEmptyIdentifierTest() {
-        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("", schemaContext);
-        assertEquals("Returned not expected identifier",
-                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
-    }
-
-    /**
-     * Api path can be empty. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
-     * Test when identifier contains {@link RestconfConstants#MOUNT}.
-     */
-    @Test
-    public void toInstanceIdentifierEmptyIdentifierMountPointTest() {
         final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
-                "" + "/" + RestconfConstants.MOUNT, schemaContext);
+                "", schemaContext, Optional.absent());
         assertEquals("Returned not expected identifier",
                 YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
     }
@@ -210,7 +210,7 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierInvalidIdentifierNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        ParserIdentifier.toInstanceIdentifier(INVALID_TEST_IDENT, schemaContext);
+        ParserIdentifier.toInstanceIdentifier(INVALID_TEST_IDENT, schemaContext, Optional.absent());
     }
 
     /**
@@ -220,7 +220,48 @@ public class ParserIdentifierTest {
     @Test
     public void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        ParserIdentifier.toInstanceIdentifier(INVALID_MOUNT_POINT_IDENT, schemaContext);
+        ParserIdentifier.toInstanceIdentifier(INVALID_MOUNT_POINT_IDENT, schemaContext, Optional.of(mountPointService));
+    }
+
+    /**
+     * Negative test when <code>DOMMountPoint</code> cannot be found. Test is expected to fail with
+     * <code>RestconfDocumentedException</code> error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void toInstanceIdentifierMissingMountPointNegativeTest() {
+        try {
+            ParserIdentifier.toInstanceIdentifier(
+                    "" + "/" + RestconfConstants.MOUNT, schemaContext, Optional.of(mountPointService));
+            fail("Test should fail due to missing mount point");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test when <code>{@link DOMMountPointService}</code> is absent. Test is expected to fail with
+     * <code>RestconfDocumentedException</code> error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void toInstanceIdentifierMissingMountPointServiceNegativeTest() {
+        try {
+            ParserIdentifier.toInstanceIdentifier(RestconfConstants.MOUNT, schemaContext, Optional.absent());
+            fail("Test should fail due to absent mount point service");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    ErrorType.APPLICATION, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    ErrorTag.OPERATION_FAILED, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    500, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
     }
 
     /**