Merge "Bug 6198 - Use sal-netconf-connector to connet device costs too much time"
authorTomas Cere <tcere@cisco.com>
Thu, 11 Aug 2016 08:32:17 +0000 (08:32 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 11 Aug 2016 08:32:17 +0000 (08:32 +0000)
127 files changed:
features/netconf-connector/pom.xml
features/netconf/pom.xml
features/pom.xml
features/restconf/pom.xml
features/yanglib/pom.xml
karaf/pom.xml
netconf/aaa-authn-odl-plugin/pom.xml
netconf/abstract-topology/pom.xml
netconf/config-netconf-connector/pom.xml
netconf/mdsal-netconf-connector/pom.xml
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java
netconf/mdsal-netconf-monitoring/pom.xml
netconf/mdsal-netconf-notification/pom.xml
netconf/mdsal-netconf-yang-library/pom.xml
netconf/messagebus-netconf/pom.xml
netconf/models/ietf-netconf-monitoring-extension/pom.xml
netconf/models/ietf-netconf-monitoring/pom.xml
netconf/models/ietf-netconf-notifications/pom.xml
netconf/models/ietf-netconf-yang-library/pom.xml
netconf/models/ietf-netconf/pom.xml
netconf/models/pom.xml
netconf/netconf-api/pom.xml
netconf/netconf-artifacts/pom.xml
netconf/netconf-auth/pom.xml
netconf/netconf-client/pom.xml
netconf/netconf-config-dispatcher/pom.xml
netconf/netconf-config/pom.xml
netconf/netconf-connector-config/pom.xml
netconf/netconf-console/pom.xml
netconf/netconf-impl/pom.xml
netconf/netconf-it/pom.xml
netconf/netconf-mapping-api/pom.xml
netconf/netconf-mdsal-config/pom.xml
netconf/netconf-monitoring/pom.xml
netconf/netconf-netty-util/pom.xml
netconf/netconf-notifications-api/pom.xml
netconf/netconf-notifications-impl/pom.xml
netconf/netconf-ssh/pom.xml
netconf/netconf-tcp/pom.xml
netconf/netconf-topology-config/pom.xml
netconf/netconf-topology-config/src/main/resources/initial/02-clustered-netconf-topology.xml
netconf/netconf-topology/pom.xml
netconf/netconf-util/pom.xml
netconf/pom.xml
netconf/sal-netconf-connector/pom.xml
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/SchemalessNetconfDeviceRpcTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/YangLibrarySchemaYangSourceProviderTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/util/SchemalessRpcStructureTransformerTest.java
netconf/tools/netconf-cli/pom.xml
netconf/tools/netconf-testtool/pom.xml
netconf/tools/pom.xml
netconf/yanglib/pom.xml
pom.xml
restconf/pom.xml
restconf/restconf-artifacts/pom.xml
restconf/sal-rest-connector-config/pom.xml
restconf/sal-rest-connector/pom.xml
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/AbstractIdentifierAwareJaxRsProvider.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPATCHBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHJsonBodyWriter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/PATCHXmlBodyWriter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEditOperation.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PATCHEntity.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfDocumentedException.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfError.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfProviderImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/websockets/WebSocketServerHandler.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/RestconfApplication.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializer.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierSerializer.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/DeleteDataTransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtil.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.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/restful/utils/RestconfDataServiceConstant.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/TransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/parser/ParserIdentifier.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11AbstractIdentifierAwareJaxRsProvider.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11JsonToPATCHBodyReader.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11StringModuleInstanceIdentifierCodec.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11XmlToPATCHBodyReader.java [new file with mode: 0644]
restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/Draft11AbstractBodyReaderTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11JsonPATCHBodyReader.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11XmlPATCHBodyReader.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReader.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReader.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/input/to/cnsn/test/RestPutListDataTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestDeleteOperationTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutConfigTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/IdentifierCodecTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierSerializerTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfModulesServiceTestUtils.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/DeleteDataTransactionUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnList.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdata.json
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueMissing.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueNotSupported.json [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueMissing.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml [new file with mode: 0644]
restconf/sal-rest-docgen-maven/pom.xml
restconf/sal-rest-docgen/pom.xml
restconf/sal-restconf-broker/pom.xml

index 8359830ff39f32b5fb5dd94414faad6dfd8f133b..17b13c449edf924c60471d364aafcf4a30aa6955 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>features-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>features-netconf-connector</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <properties>
-    <commons.opendaylight.version>1.7.0-SNAPSHOT</commons.opendaylight.version>
-    <controller.mdsal.version>1.4.0-SNAPSHOT</controller.mdsal.version>
-    <config.version>0.5.0-SNAPSHOT</config.version>
-    <features.test.version>1.7.0-SNAPSHOT</features.test.version>
-    <mdsal.version>2.1.0-SNAPSHOT</mdsal.version>
-    <mdsal.model.version>0.9.0-SNAPSHOT</mdsal.model.version>
-    <netconf.version>1.1.0-SNAPSHOT</netconf.version>
-    <netconf.connector.version>1.4.0-SNAPSHOT</netconf.connector.version>
-    <yangtools.version>1.0.0-SNAPSHOT</yangtools.version>
+    <commons.opendaylight.version>1.8.0-SNAPSHOT</commons.opendaylight.version>
+    <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
+    <config.version>0.6.0-SNAPSHOT</config.version>
+    <features.test.version>1.8.0-SNAPSHOT</features.test.version>
+    <mdsal.version>2.2.0-SNAPSHOT</mdsal.version>
+    <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
+    <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+    <netconf.connector.version>1.5.0-SNAPSHOT</netconf.connector.version>
+    <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
 
     <features.file>features.xml</features.file>
     <config.configfile.directory>etc/opendaylight/karaf</config.configfile.directory>
@@ -44,7 +44,7 @@
       <dependency>
         <groupId>org.opendaylight.netconf</groupId>
         <artifactId>netconf-artifacts</artifactId>
-        <version>1.1.0-SNAPSHOT</version>
+        <version>1.2.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
index 3e0a88d708afa41ac3fa7c1cfea7002d62ca5f61..09f63aaef39d3f1d6a34227be022d46c9fcc9eec 100644 (file)
@@ -4,25 +4,25 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>features-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>features-netconf</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <properties>
-    <aaa.version>0.4.0-SNAPSHOT</aaa.version>
-    <commons.opendaylight.version>1.7.0-SNAPSHOT</commons.opendaylight.version>
-    <controller.mdsal.version>1.4.0-SNAPSHOT</controller.mdsal.version>
-    <config.version>0.5.0-SNAPSHOT</config.version>
-    <features.test.version>1.7.0-SNAPSHOT</features.test.version>
-    <mdsal.model.version>0.9.0-SNAPSHOT</mdsal.model.version>
-    <netconf.version>1.1.0-SNAPSHOT</netconf.version>
-    <protocol-framework.version>0.8.0-SNAPSHOT</protocol-framework.version>
-    <yangtools.version>1.0.0-SNAPSHOT</yangtools.version>
+    <aaa.version>0.5.0-SNAPSHOT</aaa.version>
+    <commons.opendaylight.version>1.8.0-SNAPSHOT</commons.opendaylight.version>
+    <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
+    <config.version>0.6.0-SNAPSHOT</config.version>
+    <features.test.version>1.8.0-SNAPSHOT</features.test.version>
+    <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
+    <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+    <protocol-framework.version>0.9.0-SNAPSHOT</protocol-framework.version>
+    <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
     <sshd-core.version>0.14.0</sshd-core.version>
 
     <config.configfile.directory>etc/opendaylight/karaf</config.configfile.directory>
@@ -42,7 +42,7 @@
       <dependency>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent-artifacts</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
index dff44388302282a1bd59c22d29e29d72ac961e53..1790fcf490df69926858072b962dedbb548fee3c 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-parent</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <relativePath>..</relativePath>
   </parent>
   <artifactId>netconf-features-parent</artifactId>
index 30ad4c15f86dd34ad7a81887e5696f3cc7390c27..0c866ab00b38053c2c4296e83cacf449d3e2fe8b 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>features-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
   <artifactId>features-restconf</artifactId>
   <groupId>org.opendaylight.netconf</groupId>
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <properties>
-    <aaa.version>0.4.0-SNAPSHOT</aaa.version>
-    <commons.opendaylight.version>1.7.0-SNAPSHOT</commons.opendaylight.version>
-    <controller.mdsal.version>1.4.0-SNAPSHOT</controller.mdsal.version>
-    <features.test.version>1.7.0-SNAPSHOT</features.test.version>
-    <mdsal.version>2.1.0-SNAPSHOT</mdsal.version>
-    <mdsal.model.version>0.9.0-SNAPSHOT</mdsal.model.version>
-    <restconf.version>1.4.0-SNAPSHOT</restconf.version>
-    <yangtools.version>1.0.0-SNAPSHOT</yangtools.version>
+    <aaa.version>0.5.0-SNAPSHOT</aaa.version>
+    <commons.opendaylight.version>1.8.0-SNAPSHOT</commons.opendaylight.version>
+    <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
+    <features.test.version>1.8.0-SNAPSHOT</features.test.version>
+    <mdsal.version>2.2.0-SNAPSHOT</mdsal.version>
+    <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
+    <restconf.version>1.5.0-SNAPSHOT</restconf.version>
+    <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
 
     <features.file>features.xml</features.file>
     <config.configfile.directory>etc/opendaylight/karaf</config.configfile.directory>
@@ -40,7 +40,7 @@
       <dependency>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent-artifacts</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
index 002de184e104af707e46dfe12c8990fe05433c10..fecd2fbab9cd865bd240a7d7783340b8085bc76e 100644 (file)
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>features-parent</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>features-yanglib</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <properties>
-        <controller.mdsal.version>1.4.0-SNAPSHOT</controller.mdsal.version>
-        <netconf.version>1.1.0-SNAPSHOT</netconf.version>
-        <mdsal.version>2.1.0-SNAPSHOT</mdsal.version>
-        <mdsal.model.version>0.9.0-SNAPSHOT</mdsal.model.version>
-        <restconf.version>1.4.0-SNAPSHOT</restconf.version>
-        <yangtools.version>1.0.0-SNAPSHOT</yangtools.version>
+        <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
+        <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+        <mdsal.version>2.2.0-SNAPSHOT</mdsal.version>
+        <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
+        <restconf.version>1.5.0-SNAPSHOT</restconf.version>
+        <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
     </properties>
 
     <dependencyManagement>
@@ -35,7 +35,7 @@
             <dependency>
                 <groupId>org.opendaylight.odlparent</groupId>
                 <artifactId>odlparent-artifacts</artifactId>
-                <version>1.7.0-SNAPSHOT</version>
+                <version>1.8.0-SNAPSHOT</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
index 18710a6c647e3ebcb137ad248f9e7ba8cda81246..09f022f097226dfc6fe1571678547bb784283eb0 100644 (file)
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>karaf-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath></relativePath>
   </parent>
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-karaf</artifactId>
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <prerequisites>
     <maven>3.1.1</maven>
   </prerequisites>
   <properties>
-    <netconf.version>1.1.0-SNAPSHOT</netconf.version>
-    <restconf.version>1.4.0-SNAPSHOT</restconf.version>
+    <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+    <restconf.version>1.5.0-SNAPSHOT</restconf.version>
   </properties>
 
   <dependencies>
index abdf3c6123d04e19cd4f0fe8b77f8e394d9689ca..9d15ad4c5f4a1cfdefa6104625728b3976f7e89c 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>aaa-authn-odl-plugin</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
index b6d75095377ea1a52ce41cee1c017d744df6d844..e4bfcee85c502fcd040a1e9084ad00526ec7b909 100644 (file)
@@ -7,13 +7,13 @@
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>bundle-parent</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>abstract-topology</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
index a8058468ee0969d40f7ed3c6ab8c00320489ec8d..1a3b358046f13d9e53065d5f5a9794b8bd1d7f76 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>config-netconf-connector</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index cf5878c82bb45bd6e91b9b8bff34360ee6ff9dbc..fa5849658fec4645e6eb6c60bc90d46bef4d0f79 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>mdsal-netconf-connector</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index bd0b59f53960eaff5aad6be9c638604c5a269e23..4193e3150e4cf460b392d84f2e6d30a2c69f8d07 100644 (file)
@@ -1,3 +1,10 @@
+/*
+ * 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.netconf.mdsal.connector.ops.get;
 
 import static org.mockito.Mockito.doReturn;
@@ -96,4 +103,4 @@ public class FilterContentValidatorTest {
         }
         return reactor.buildEffective();
     }
-}
\ No newline at end of file
+}
index a0bd15c425701f46be0b107d3bc84421de8ac7fc..c1176125d4a4f855e983be6a8453db3eaabda50a 100644 (file)
@@ -4,13 +4,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>mdsal-netconf-monitoring</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 9050e83b25c9a2fea11fca0fcf338aefe17f07c7..7c9543c1849d7599e0bc61c638217a3d009e1a9b 100644 (file)
@@ -4,13 +4,13 @@
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>mdsal-netconf-notification</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <name>${project.artifactId}</name>
     <packaging>bundle</packaging>
 
index 75f746644aaa14fa146d19a85bdf1701f6611c49..c7063795cb298773f3fac7c9d184ffeac925cac4 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>mdsal-netconf-yang-library</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <name>${project.artifactId}</name>
     <packaging>bundle</packaging>
 
index 7559fdd5d468100e7576fcf7e598bed4732346d5..b8946c8f2c0443fcdcb387f0a40fb7534df726a3 100644 (file)
@@ -14,13 +14,13 @@ and is available at http://www.eclipse.org/legal/epl-v10.html
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>messagebus-netconf</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <name>${project.artifactId}</name>
     <packaging>bundle</packaging>
 
index 2735087e8fcfdc0c027e8b6498587fac3e6307a1..881aa29c8c0963f5b1897627c65d3d5a4221f670 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.mdsal</groupId>
     <artifactId>binding-parent</artifactId>
-    <version>0.9.0-SNAPSHOT</version>
+    <version>0.10.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>ietf-netconf-monitoring-extension</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index c68d142007751d5cd9b83729c46af70b7d60ed25..ad83a242e123fc6ca9836d5b49f3af8d4e7fc76e 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.mdsal</groupId>
     <artifactId>binding-parent</artifactId>
-    <version>0.9.0-SNAPSHOT</version>
+    <version>0.10.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>ietf-netconf-monitoring</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index a5065dc6e2df9b50244daa5cd96621248c45ca5f..227b235271396c9cf54db1a93812126cdce705c4 100644 (file)
   <parent>
     <groupId>org.opendaylight.mdsal</groupId>
     <artifactId>binding-parent</artifactId>
-    <version>0.9.0-SNAPSHOT</version>
+    <version>0.10.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
   
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>ietf-netconf-notifications</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index dc9152111f7e1dc77d03b59a395c6af3de71db47..e52cf887ffbfe47ada53d4977fff209ce10f97eb 100644 (file)
     <parent>
         <groupId>org.opendaylight.mdsal</groupId>
         <artifactId>binding-parent</artifactId>
-        <version>0.9.0-SNAPSHOT</version>
+        <version>0.10.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>ietf-netconf-yang-library</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <name>${project.artifactId}</name>
     <packaging>bundle</packaging>
 
index 3fb1fdb4a92d89e8203eb2c026191ecf2d72303a..183396e140f743d6f5121268886d5a4532aacf24 100644 (file)
     <parent>
         <groupId>org.opendaylight.mdsal</groupId>
         <artifactId>binding-parent</artifactId>
-       <version>0.9.0-SNAPSHOT</version>
+       <version>0.10.0-SNAPSHOT</version>
        <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>ietf-netconf</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
     <name>${project.artifactId}</name>
 
index 456d7cc6af88e2d2dee3a153cc8defd1e10b7274..ec2eb9ce4df344d7eb677226187b7d5267323bde 100644 (file)
@@ -5,7 +5,7 @@
   <parent>
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-subsystem</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>netconf-models</artifactId>
index 4da8ab90770b3fb76fdb628dedbc65bc2e2cd1cf..450097d36e1e43a69475a2e395e90df88709bfef 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-api</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 30dfecbb417211275f336f1b2805ae1836848f7f..4a8b22096a563dbb874e7c592797bbe75d17c58c 100644 (file)
     <parent>
       <groupId>org.opendaylight.odlparent</groupId>
       <artifactId>odlparent-lite</artifactId>
-      <version>1.7.0-SNAPSHOT</version>
+      <version>1.8.0-SNAPSHOT</version>
       <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-artifacts</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <properties>
-        <mdsal.version>1.4.0-SNAPSHOT</mdsal.version>
+        <mdsal.version>1.5.0-SNAPSHOT</mdsal.version>
     </properties>
 
     <dependencyManagement>
index 28cac1da27106f592e903d7c293cbacf8080c07d..187561078af52d6d85eb32dd3029771c0db64b58 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-auth</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <name>${project.artifactId}</name>
     <packaging>bundle</packaging>
 </project>
index fa9bccd53ff68801175969657a7b110c3d08a19c..914d8647d85eabcb3b99bc64b4805cb056f1d92e 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-client</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 66711a5ac1efef83ac2f38da18ad672f7fa58e94..fabef7d7ab7a79e24a22c4bca44e9ec09c067e9e 100644 (file)
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-config-dispatcher</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
     <dependencyManagement>
index fc700930cfb17cbde174497df517aeeb0eb3b800..b44c4c164de0e0f8b043af237bf41d36514d409b 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-config</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <description>Configuration files for netconf</description>
   <packaging>jar</packaging>
 
index 811af20cf74facf75d073876d0a6598096f4a3be..ff6e0ec1776b4c5b5bc9525ccdef9c37c6b5ed64 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent-lite</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-connector-config</artifactId>
   <description>Configuration files for netconf-connector</description>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <build>
index 31cefd6884c09362a590cc89d6a79c764d12098a..8e4e05f93c1a1e701ac9596320f153c71ef14591 100644 (file)
@@ -11,13 +11,13 @@ and is available at http://www.eclipse.org/legal/epl-v10.html INTERNAL
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-console</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index f7689899af30fa57364325c63e3c423296d69f58..9a7ef9aff777bea17fbfa53c085a95b848711ee5 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-impl</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 968fa61596e4723d29bfe5f0b84489ff6fbd95e6..7bee36356c698299ec0df33ad8405f917496b2c6 100644 (file)
@@ -5,7 +5,7 @@
   <parent>
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-subsystem</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>netconf-it</artifactId>
index 029605197cee7ed055c6e52e7ff3db701c0220ff..0bb747f1a8b1383235910a05a45a9929cafc63f6 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-mapping-api</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 6f1af5afaf37de403f79cf567cbc7abb1c8cb2d2..3a455207d6791a49a46d076eab2d8a2b8d4e300b 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent-lite</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-mdsal-config</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <description>Configuration files for netconf for mdsal</description>
   <packaging>jar</packaging>
 
index d44c287eeba77227b65c1958c73546c37027db87..84bfccbb14a0f487f4a692c39d5313194e7cece9 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-monitoring</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 4ac2772ca3d6f37a74aa2467a66bae68cf5c9df7..eb34e019f66fc222e2a0880c8ad72bd0af6a0333 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-netty-util</artifactId>
   <packaging>bundle</packaging>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
 
   <dependencyManagement>
index f93d720abd6622ea096dcd31638e9ddc9848ea2c..d2fed42496e7ca83bf9b5b6d6f73216d89c2934c 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-notifications-api</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
index ecaadf63326ff35e662543043af85601fd91e0b3..93d7f95dbca22ce5ec9be38708c5e722d0dc050c 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-notifications-impl</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
index 7a7cbde87d57af781d49a8f8790d1783a49f3968..78d0bfae28ec7a59b85cdde0c09c57cc0ecfabc1 100644 (file)
@@ -4,13 +4,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-ssh</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 8a743d99210923c995800dbd62ef6d51cadbf072..f6c8c05f0539260883847f988c463b6b87b301ed 100644 (file)
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-tcp</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index ab5a2f8cc7d5648f4a4ee71b5fbcd1ce782ab3ef..49f2a0011291389821a1df67cde523646c920ffb 100644 (file)
@@ -7,13 +7,13 @@
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent-lite</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-topology-config</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <description>Configuration files for netconf topology</description>
     <packaging>jar</packaging>
 
index 39bc8541b74aebdad1f3d23b47848a636671c4f8..ded67f5ae6529f5135db171490b3cea75c779047 100644 (file)
@@ -68,7 +68,9 @@
     <required-capabilities>
         <capability>urn:opendaylight:params:xml:ns:yang:controller:netconf:topology?module=netconf-topology&amp;revision=2015-07-27</capability>
         <capability>urn:opendaylight:params:xml:ns:yang:controller:clustered:netconf:topology?module=clustered-netconf-topology&amp;revision=2015-11-04</capability>
-        <capability>urn:opendaylight:params:xml:ns:yang:controller:config:distributed-entity-ownership-service?module=distributed-entity-ownership-service&amp;revision=2015-08-10</capability>
+        <capability>
+            urn:opendaylight:params:xml:ns:yang:controller:config:legacy-entity-ownership-service-provider?module=opendaylight-legacy-entity-ownership-service-provider&amp;revision=2016-02-26
+        </capability>
         <capability>urn:opendaylight:params:xml:ns:yang:controller:config:actor-system-provider:service?module=actor-system-provider-service&amp;revision=2015-10-05</capability>
     </required-capabilities>
 </snapshot>
\ No newline at end of file
index 08b0dbac789e73e428b08abf8e7614ad7dd73742..b2488bd0ff36841e78ea55710aea7b62ad20025a 100644 (file)
@@ -6,13 +6,13 @@
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-topology</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <dependencyManagement>
index 154dedf38069fa6b85c265d0c95ef9f88e678fea..954ae98d5c7cd4d3cacd657946fc8dbde170bdd2 100644 (file)
@@ -5,13 +5,13 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-util</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <packaging>bundle</packaging>
 
index 800add5a7bdc4940fd8128fe86d9c68d93d7461d..d3e2f76f2ec3de83409cb50488faee278ca98728 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent-lite</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-subsystem</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>pom</packaging>
   <name>${project.artifactId}</name>
 
@@ -80,7 +80,7 @@
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>protocol-framework</artifactId>
-        <version>0.8.0-SNAPSHOT</version>
+        <version>0.9.0-SNAPSHOT</version>
       </dependency>
     </dependencies>
   </dependencyManagement>
index 89ff7b3ba032200f35f2138a6f7f1bd2a2fb9ab4..a1687e6cceba0032759ccfede09ae2055d1d436b 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
@@ -12,7 +12,7 @@
   <artifactId>sal-netconf-connector</artifactId>
 
   <!-- Preserve version from mdsal -->
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
   <dependencyManagement>
@@ -20,7 +20,7 @@
       <dependency>
         <groupId>org.opendaylight.netconf</groupId>
         <artifactId>netconf-artifacts</artifactId>
-        <version>1.1.0-SNAPSHOT</version>
+        <version>1.2.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
index 237021930a9a28457315bee4f05dfe3ace3a2215..80f97b1ab0819ccc8618b66938ce1d6ae0c49055 100644 (file)
@@ -1,3 +1,10 @@
+/*
+ * 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.netconf.sal.connect.netconf.sal;
 
 import static org.mockito.Matchers.any;
@@ -73,4 +80,4 @@ public class SchemalessNetconfDeviceRpcTest {
         verify(listener).sendRequest(msgCaptor.capture(), qNameCaptor.capture());
         System.out.println(XmlUtil.toString(msgCaptor.getValue().getDocument()));
     }
-}
\ No newline at end of file
+}
index 916a5f8e2605d56e3194062db24c83b114ee2f6e..5eca12fc28f368f036dc7ecfe44f9d676c677036 100644 (file)
@@ -1,3 +1,10 @@
+/*
+ * 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.netconf.sal.connect.netconf.schema;
 
 import com.google.common.base.Optional;
@@ -56,4 +63,4 @@ public class YangLibrarySchemaYangSourceProviderTest {
     public void testGetSourceNotAvailable() throws Exception {
         yangLibrarySchemaYangSourceProvider.getSource(RevisionSourceIdentifier.create("aaaaa", "0000-00-00"));
     }
-}
\ No newline at end of file
+}
index ea8ad18929c65901c4ade9c358f603ce002f3bd5..697aed3085c6c81f831a3bd05415021a43de0c18 100644 (file)
@@ -1,3 +1,10 @@
+/*
+ * 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.netconf.sal.connect.netconf.util;
 
 import com.google.common.base.Optional;
@@ -137,4 +144,4 @@ public class SchemalessRpcStructureTransformerTest {
     private static YangInstanceIdentifier.NodeIdentifierWithPredicates createListNodeId(String nodeName, Map<QName, Object> keys) {
         return new YangInstanceIdentifier.NodeIdentifierWithPredicates(QName.create(NAMESPACE, nodeName), keys);
     }
-}
\ No newline at end of file
+}
index f0be99b87e93117972971ad1ac678cfadbd411f0..12a0d729caa2fe8235c7d00c2a8079c80da7ef86 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>netconf-cli</artifactId>
-  <version>1.1.0-SNAPSHOT</version>
+  <version>1.2.0-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>${project.artifactId}</name>
 
index b6b51503a49377ddaf6a76cf5a668000f407e131..16ecdca63e2623b7fbf898f5063f28e530284c45 100644 (file)
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-testtool</artifactId>
     <name>${project.artifactId}</name>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <properties>
index eeb780b6155c439397809444f2a0d00755d40a69..81b21a90d0b0927d05127f0a23f05148bb228397 100644 (file)
@@ -5,7 +5,7 @@
   <parent>
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-subsystem</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>netconf-tools</artifactId>
index 232a2febf906843223492bb3d7769917d98c9e58..2252252e110743de3f22f48b9a081cdd1da41e75 100644 (file)
     <parent>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>config-parent</artifactId>
-        <version>0.5.0-SNAPSHOT</version>
+        <version>0.6.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>yanglib</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <build>
diff --git a/pom.xml b/pom.xml
index 51c4dad6c5b18039f84d61222fac3d0769fa0790..a209da627e85337d62f51c88b4cf1fcd7701ec44 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -5,13 +5,13 @@
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent-lite</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>netconf-parent</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>${project.artifactId}</name>
 
     </modules>
 
     <properties>
-        <config.version>0.5.0-SNAPSHOT</config.version>
+        <config.version>0.6.0-SNAPSHOT</config.version>
         <config.configfile.directory>etc/opendaylight/karaf</config.configfile.directory>
-        <features.test.version>1.7.0-SNAPSHOT</features.test.version>
+        <features.test.version>1.8.0-SNAPSHOT</features.test.version>
 
-        <mdsal.version>2.1.0-SNAPSHOT</mdsal.version>
-        <mdsal.model.version>0.9.0-SNAPSHOT</mdsal.model.version>
-        <controller.mdsal.version>1.4.0-SNAPSHOT</controller.mdsal.version>
-        <netconf.version>1.1.0-SNAPSHOT</netconf.version>
-        <restconf.version>1.4.0-SNAPSHOT</restconf.version>
-        <yangtools.version>1.0.0-SNAPSHOT</yangtools.version>
+        <mdsal.version>2.2.0-SNAPSHOT</mdsal.version>
+        <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
+        <controller.mdsal.version>1.5.0-SNAPSHOT</controller.mdsal.version>
+        <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+        <restconf.version>1.5.0-SNAPSHOT</restconf.version>
+        <yangtools.version>1.1.0-SNAPSHOT</yangtools.version>
     </properties>
 
 
@@ -63,7 +63,7 @@
         <dependency>
           <groupId>org.opendaylight.aaa</groupId>
           <artifactId>aaa-artifacts</artifactId>
-          <version>0.4.0-SNAPSHOT</version>
+          <version>0.5.0-SNAPSHOT</version>
           <type>pom</type>
           <scope>import</scope>
         </dependency>
@@ -94,7 +94,7 @@
                         <dependency>
                             <groupId>org.opendaylight.controller</groupId>
                             <artifactId>checkstyle</artifactId>
-                            <version>0.3.0-SNAPSHOT</version>
+                            <version>0.4.0-SNAPSHOT</version>
                         </dependency>
                     </dependencies>
                 </plugin>
index 544dc580120ebdcd7201ff131d4702b221628e3a..85963438623e3ce7d3ae417bcba9466e133cddb9 100644 (file)
@@ -5,13 +5,13 @@
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent-lite</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>restconf-parent</artifactId>
-    <version>1.4.0-SNAPSHOT</version>
+    <version>1.5.0-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>${project.artifactId}</name>
 
index cb715b930dd4105919cd2d59b6618160288dd7d8..457e555c70517e0c54dec7ba4afcd70fd4631ada 100644 (file)
@@ -8,13 +8,13 @@
     <parent>
       <groupId>org.opendaylight.odlparent</groupId>
       <artifactId>odlparent-lite</artifactId>
-      <version>1.7.0-SNAPSHOT</version>
+      <version>1.8.0-SNAPSHOT</version>
       <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>restconf-artifacts</artifactId>
-    <version>1.4.0-SNAPSHOT</version>
+    <version>1.5.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <dependencyManagement>
index dea016fe4f009d1c2eedd692192337101d512018..40981c58778ae29a695ece271e8fc1c811fcbc9d 100644 (file)
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>odlparent-lite</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>sal-rest-connector-config</artifactId>
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <description>Configuration files for sal-rest-connector</description>
 
   <packaging>jar</packaging>
index 84e5e8e8c43eddeb0b213f9211466c540b832eee..13409322d3322700afb3ae0fe9246ec9f0d4dfca 100644 (file)
@@ -4,13 +4,13 @@
   <parent>
     <groupId>org.opendaylight.controller</groupId>
     <artifactId>config-parent</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
+    <version>0.6.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>sal-rest-connector</artifactId>
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
   <dependencyManagement>
@@ -18,7 +18,7 @@
       <dependency>
         <groupId>org.opendaylight.netconf</groupId>
         <artifactId>netconf-parent</artifactId>
-        <version>1.1.0-SNAPSHOT</version>
+        <version>1.2.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <version>1.2</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.json</groupId>
+      <artifactId>json</artifactId>
+    </dependency>
+
     <!-- Testing Dependencies -->
     <dependency>
       <groupId>org.glassfish.jersey.test-framework.providers</groupId>
index 243858c9c20da3fee4e304ea908755cb958eff0e..84aea20d74cc15afeb1cc710469ab5b956eb50cf 100644 (file)
@@ -14,7 +14,13 @@ import javax.ws.rs.core.UriInfo;
 import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.restconf.utils.patch.Draft11AbstractIdentifierAwareJaxRsProvider;
 
+/**
+ * @deprecated This class will be replaced by
+ * {@link Draft11AbstractIdentifierAwareJaxRsProvider}
+ */
+@Deprecated
 public class AbstractIdentifierAwareJaxRsProvider {
 
     private static final String POST = "POST";
@@ -26,7 +32,7 @@ public class AbstractIdentifierAwareJaxRsProvider {
     private Request request;
 
     protected final String getIdentifier() {
-        return uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
+        return this.uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
     }
 
     protected InstanceIdentifierContext<?> getInstanceIdentifierContext() {
@@ -34,10 +40,10 @@ public class AbstractIdentifierAwareJaxRsProvider {
     }
 
     protected UriInfo getUriInfo() {
-        return uriInfo;
+        return this.uriInfo;
     }
 
     protected boolean isPost() {
-        return POST.equals(request.getMethod());
+        return POST.equals(this.request.getMethod());
     }
 }
index 6d3f7ea34feab0c101cd7b6b08a98b4b79a34b24..a9aa44c2c9f8c414af69564d850127e62be406ea 100644 (file)
@@ -8,19 +8,20 @@
 
 package org.opendaylight.netconf.sal.rest.impl;
 
-import com.google.common.base.Preconditions;
+import static org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation.isPatchOperationWithValue;
+
 import com.google.common.collect.ImmutableList;
 import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -36,36 +37,43 @@ import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
-import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.restconf.utils.patch.Draft11JsonToPATCHBodyReader;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
 import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
-import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * @deprecated This class will be replaced by {@link Draft11JsonToPATCHBodyReader}
+ */
+@Deprecated
 @Provider
 @Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON})
-public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<PATCHContext> {
+public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider
+        implements MessageBodyReader<PATCHContext> {
 
     private final static Logger LOG = LoggerFactory.getLogger(JsonToPATCHBodyReader.class);
     private String patchId;
 
     @Override
-    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+    public boolean isReadable(final Class<?> type, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
         return true;
     }
 
     @Override
-    public PATCHContext readFrom(Class<PATCHContext> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
+    public PATCHContext readFrom(final Class<PATCHContext> type, final Type genericType,
+                                 final Annotation[] annotations, final MediaType mediaType,
+                                 final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+            throws IOException, WebApplicationException {
         try {
             return readFrom(getInstanceIdentifierContext(), entityStream);
         } catch (final Exception e) {
@@ -73,14 +81,13 @@ public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider
         }
     }
 
-    private static RuntimeException propagateExceptionAs(Exception e) throws RestconfDocumentedException {
-        if(e instanceof RestconfDocumentedException) {
+    private static RuntimeException propagateExceptionAs(final Exception e) throws RestconfDocumentedException {
+        if (e instanceof RestconfDocumentedException) {
             throw (RestconfDocumentedException)e;
         }
 
-        if(e instanceof ResultAlreadySetException) {
+        if (e instanceof ResultAlreadySetException) {
             LOG.debug("Error parsing json input:", e);
-
             throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
         }
 
@@ -98,7 +105,8 @@ public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider
         }
     }
 
-    private PATCHContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream) throws IOException {
+    private PATCHContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException {
         if (entityStream.available() < 1) {
             return new PATCHContext(path, null, null);
         }
@@ -110,15 +118,11 @@ public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider
         return new PATCHContext(path, resultList, patchId);
     }
 
-    private List<PATCHEntity> read(final JsonReader in, InstanceIdentifierContext path) throws
-            IOException {
-
-        boolean inEdit = false;
-        boolean inValue = false;
-        String operation = null;
-        String target = null;
-        String editId = null;
-        List<PATCHEntity> resultCollection = new ArrayList<>();
+    private List<PATCHEntity> read(final JsonReader in, final InstanceIdentifierContext path) throws IOException {
+        final List<PATCHEntity> resultCollection = new ArrayList<>();
+        final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+                path.getSchemaContext());
+        final JsonToPATCHBodyReader.PatchEdit edit = new JsonToPATCHBodyReader.PatchEdit();
 
         while (in.hasNext()) {
             switch (in.peek()) {
@@ -136,105 +140,318 @@ public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider
                     in.beginArray();
                     break;
                 case BEGIN_OBJECT:
-                    if (inEdit && operation != null & target != null & inValue) {
-                        //let's do the stuff - find out target node
-//                      StringInstanceIdentifierCodec codec = new StringInstanceIdentifierCodec(path
-//                               .getSchemaContext());
-//                        if (path.getInstanceIdentifier().toString().equals("/")) {
-//                        final YangInstanceIdentifier deserialized = codec.deserialize(target);
-//                        }
-                        DataSchemaNode targetNode = ((DataNodeContainer)(path.getSchemaNode())).getDataChildByName
-                                (target.replace("/", ""));
-                        if (targetNode == null) {
-                            LOG.debug("Target node {} not found in path {} ", target, path.getSchemaNode());
-                            throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
-                                    ErrorTag.MALFORMED_MESSAGE);
-                        } else {
-
-                            final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
-                            final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
-
-                            //keep on parsing json from place where target points
-                            final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(),
-                                    path.getSchemaNode());
-                            jsonParser.parse(in);
-
-                            final YangInstanceIdentifier targetII = path.getInstanceIdentifier().node(targetNode.getQName());
-                            resultCollection.add(new PATCHEntity(editId, operation, targetII, resultHolder.getResult
-                                    ()));
-                            inValue = false;
-
-                            operation = null;
-                            target = null;
-                        }
-                        in.endObject();
-                    } else {
-                        in.beginObject();
-                    }
+                    in.beginObject();
                     break;
                 case END_DOCUMENT:
                     break;
                 case NAME:
-                    final String name = in.nextName();
-
-                    switch (name) {
-                        case "edit" : inEdit = true;
-                            break;
-                        case "operation" : operation = in.nextString();
-                            break;
-                        case "target" : target = in.nextString();
-                            break;
-                        case "value" : inValue = true;
-                            break;
-                        case "patch-id" : patchId = in.nextString();
-                            break;
-                        case "edit-id" : editId = in.nextString();
-                            break;
-                    }
+                    parseByName(in.nextName(), edit, in, path, codec, resultCollection);
                     break;
                 case END_OBJECT:
                     in.endObject();
                     break;
-            case END_ARRAY:
-                in.endArray();
-                break;
+                case END_ARRAY:
+                    in.endArray();
+                    break;
 
+                default:
+                    break;
+            }
+        }
+
+        return ImmutableList.copyOf(resultCollection);
+    }
+
+    /**
+     * Switch value of parsed JsonToken.NAME and read edit definition or patch id
+     * @param name value of token
+     * @param edit PatchEdit instance
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext context
+     * @param codec StringModuleInstanceIdentifierCodec codec
+     * @param resultCollection collection of parsed edits
+     * @throws IOException
+     */
+    private void parseByName(@Nonnull final String name, @Nonnull final PatchEdit edit,
+                             @Nonnull final JsonReader in, @Nonnull final InstanceIdentifierContext path,
+                             @Nonnull final StringModuleInstanceIdentifierCodec codec,
+                             @Nonnull final List<PATCHEntity> resultCollection) throws IOException {
+        switch (name) {
+            case "edit" :
+                if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                    in.beginArray();
+
+                    while (in.hasNext()) {
+                        readEditDefinition(edit, in, path, codec);
+                        resultCollection.add(prepareEditOperation(edit));
+                        edit.clear();
+                    }
+
+                    in.endArray();
+                } else {
+                    readEditDefinition(edit, in, path, codec);
+                    resultCollection.add(prepareEditOperation(edit));
+                    edit.clear();
+                }
+
+                break;
+            case "patch-id" :
+                this.patchId = in.nextString();
+                break;
             default:
                 break;
+        }
+    }
+
+    /**
+     * Read one patch edit object from Json input
+     * @param edit PatchEdit instance to be filled with read data
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext path context
+     * @param codec StringModuleInstanceIdentifierCodec codec
+     * @throws IOException
+     */
+    private void readEditDefinition(@Nonnull final PatchEdit edit, @Nonnull final JsonReader in,
+                                    @Nonnull final InstanceIdentifierContext path,
+                                    @Nonnull final StringModuleInstanceIdentifierCodec codec) throws IOException {
+        final StringBuffer value = new StringBuffer();
+        in.beginObject();
+
+        while (in.hasNext()) {
+            final String editDefinition = in.nextName();
+            switch (editDefinition) {
+                case "edit-id" :
+                    edit.setId(in.nextString());
+                    break;
+                case "operation" :
+                    edit.setOperation(in.nextString());
+                    break;
+                case "target" :
+                    // target can be specified completely in request URI
+                    final String target = in.nextString();
+                    if (target.equals("/")) {
+                        edit.setTarget(path.getInstanceIdentifier());
+                        edit.setTargetSchemaNode(path.getSchemaContext());
+                    } else {
+                        edit.setTarget(codec.deserialize(codec.serialize(path.getInstanceIdentifier()).concat(target)));
+                        edit.setTargetSchemaNode(SchemaContextUtil.findDataSchemaNode(path.getSchemaContext(),
+                                codec.getDataContextTree().getChild(edit.getTarget()).getDataSchemaNode().getPath()
+                                        .getParent()));
+                    }
+
+                    break;
+                case "value" :
+                    // save data defined in value node for next (later) processing, because target needs to be read
+                    // always first and there is no ordering in Json input
+                    readValueNode(value, in);
+                    break;
+                default:
+                    break;
             }
         }
 
-        return ImmutableList.copyOf(resultCollection);
+        in.endObject();
+
+        // read saved data to normalized node when target schema is already known
+        edit.setData(readEditData(new JsonReader(new StringReader(value.toString())), edit.getTargetSchemaNode(), path));
     }
 
-    private class StringInstanceIdentifierCodec extends AbstractStringInstanceIdentifierCodec {
+    /**
+     * Parse data defined in value node and saves it to buffer
+     * @param value Buffer to read value node
+     * @param in JsonReader reader
+     * @throws IOException
+     */
+    private void readValueNode(@Nonnull final StringBuffer value, @Nonnull final JsonReader in) throws IOException {
+        in.beginObject();
+        value.append("{");
+
+        value.append("\"" + in.nextName() + "\"" + ":");
 
-        private final DataSchemaContextTree dataContextTree;
-        private final SchemaContext context;
+        if (in.peek() == JsonToken.BEGIN_ARRAY) {
+            in.beginArray();
+            value.append("[");
 
-        StringInstanceIdentifierCodec(SchemaContext context) {
-            this.context = Preconditions.checkNotNull(context);
-            this.dataContextTree = DataSchemaContextTree.from(context);
+            while (in.hasNext()) {
+                readValueObject(value, in);
+                if (in.peek() != JsonToken.END_ARRAY) {
+                    value.append(",");
+                }
+            }
+
+            in.endArray();
+            value.append("]");
+        } else {
+            readValueObject(value, in);
         }
 
-        @Nonnull
-        @Override
-        protected DataSchemaContextTree getDataContextTree() {
-            return dataContextTree;
+        in.endObject();
+        value.append("}");
+    }
+
+    /**
+     * Parse one value object of data and saves it to buffer
+     * @param value Buffer to read value object
+     * @param in JsonReader reader
+     * @throws IOException
+     */
+    private void readValueObject(@Nonnull final StringBuffer value, @Nonnull final JsonReader in) throws IOException {
+        in.beginObject();
+        value.append("{");
+
+        while (in.hasNext()) {
+            value.append("\"" + in.nextName() + "\"");
+            value.append(":");
+
+            if (in.peek() == JsonToken.STRING) {
+                value.append("\"" + in.nextString() + "\"");
+            } else {
+                if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                    in.beginArray();
+                    value.append("[");
+
+                    while (in.hasNext()) {
+                        readValueObject(value, in);
+                        if (in.peek() != JsonToken.END_ARRAY) {
+                            value.append(",");
+                        }
+                    }
+
+                    in.endArray();
+                    value.append("]");
+                } else {
+                    readValueObject(value, in);
+                }
+            }
+
+            if (in.peek() != JsonToken.END_OBJECT) {
+                value.append(",");
+            }
+        }
+
+        in.endObject();
+        value.append("}");
+    }
+
+    /**
+     * Read patch edit data defined in value node to NormalizedNode
+     * @param in reader JsonReader reader
+     * @return NormalizedNode representing data
+     */
+    private NormalizedNode readEditData(@Nonnull final JsonReader in, @Nonnull SchemaNode targetSchemaNode,
+                                        @Nonnull InstanceIdentifierContext path) {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+        JsonParserStream.create(writer, path.getSchemaContext(), targetSchemaNode).parse(in);
+
+        return resultHolder.getResult();
+    }
+
+    /**
+     * Prepare PATCHEntity from PatchEdit instance when it satisfies conditions, otherwise throws exception
+     * @param edit Instance of PatchEdit
+     * @return PATCHEntity
+     */
+    private PATCHEntity prepareEditOperation(@Nonnull final PatchEdit edit) {
+        if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
+                && checkDataPresence(edit.getOperation(), (edit.getData() != null))) {
+            if (isPatchOperationWithValue(edit.getOperation())) {
+                // for lists allow to manipulate with list items through their parent
+                final YangInstanceIdentifier targetNode;
+                if (edit.getTarget().getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                    targetNode = edit.getTarget().getParent();
+                } else {
+                    targetNode = edit.getTarget();
+                }
+
+                return new PATCHEntity(edit.getId(), edit.getOperation(), targetNode, edit.getData());
+            } else {
+                return new PATCHEntity(edit.getId(), edit.getOperation(), edit.getTarget());
+            }
+        }
+
+        throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+    }
+
+    /**
+     * Check if data is present when operation requires it and not present when operation data is not allowed
+     * @param operation Name of operation
+     * @param hasData Data in edit are present/not present
+     * @return true if data is present when operation requires it or if there are no data when operation does not
+     * allow it, false otherwise
+     */
+    private boolean checkDataPresence(@Nonnull final String operation, final boolean hasData) {
+        if (isPatchOperationWithValue(operation)) {
+            if (hasData) {
+                return true;
+            } else {
+                return false;
+            }
+        } else  {
+            if (!hasData) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Helper class representing one patch edit
+     */
+    private static final class PatchEdit {
+        private String id;
+        private String operation;
+        private YangInstanceIdentifier target;
+        private SchemaNode targetSchemaNode;
+        private NormalizedNode data;
+
+        public String getId() {
+            return id;
+        }
+
+        public void setId(String id) {
+            this.id = id;
+        }
+
+        public String getOperation() {
+            return operation;
         }
 
-        @Nullable
-        @Override
-        protected String prefixForNamespace(@Nonnull URI namespace) {
-            final Module module = context.findModuleByNamespaceAndRevision(namespace, null);
-            return module == null ? null : module.getName();
+        public void setOperation(String operation) {
+            this.operation = operation;
         }
 
-        @Nullable
-        @Override
-        protected QName createQName(@Nonnull String prefix, @Nonnull String localName) {
-            throw new UnsupportedOperationException("Not implemented");
+        public YangInstanceIdentifier getTarget() {
+            return target;
         }
 
+        public void setTarget(YangInstanceIdentifier target) {
+            this.target = target;
+        }
+
+        public SchemaNode getTargetSchemaNode() {
+            return targetSchemaNode;
+        }
+
+        public void setTargetSchemaNode(SchemaNode targetSchemaNode) {
+            this.targetSchemaNode = targetSchemaNode;
+        }
+
+        public NormalizedNode getData() {
+            return data;
+        }
+
+        public void setData(NormalizedNode data) {
+            this.data = data;
+        }
+
+        public void clear() {
+            id = null;
+            operation = null;
+            target = null;
+            targetSchemaNode = null;
+            data = null;
+        }
     }
 }
index 66b26d66c07d952a25c5a101047e573df797793a..21e2b870452dbe80f44d6d729bfa4a866d02d839 100644 (file)
@@ -22,39 +22,45 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.Provider;
-import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
 import org.opendaylight.netconf.sal.rest.api.RestconfService;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.restconf.utils.RestconfConstants;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
 
+
 @Provider
-@Produces({MediaTypes.PATCH_STATUS + RestconfService.JSON})
+@Produces({Draft02.MediaTypes.PATCH_STATUS + RestconfService.JSON,
+        Draft11.MediaTypes.PATCH_STATUS + RestconfConstants.JSON})
 public class PATCHJsonBodyWriter implements MessageBodyWriter<PATCHStatusContext> {
 
     @Override
-    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+    public boolean isWriteable(final Class<?> type, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
         return type.equals(PATCHStatusContext.class);
     }
 
     @Override
-    public long getSize(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
-            annotations, MediaType mediaType) {
+    public long getSize(final PATCHStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
         return -1;
     }
 
     @Override
-    public void writeTo(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
-            annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
-                throws IOException, WebApplicationException {
+    public void writeTo(final PATCHStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+            throws IOException, WebApplicationException {
 
         final JsonWriter jsonWriter = createJsonWriter(entityStream);
         jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status");
         jsonWriter.beginObject();
         jsonWriter.name("patch-id").value(patchStatusContext.getPatchId());
         if (patchStatusContext.isOk()) {
-            jsonWriter.name("ok").nullValue();
+            reportSuccess(jsonWriter);
         } else {
             if (patchStatusContext.getGlobalErrors() != null) {
                 reportErrors(patchStatusContext.getGlobalErrors(), jsonWriter);
@@ -64,14 +70,14 @@ public class PATCHJsonBodyWriter implements MessageBodyWriter<PATCHStatusContext
             jsonWriter.beginObject();
             jsonWriter.name("edit");
             jsonWriter.beginArray();
-            for (PATCHStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) {
+            for (final PATCHStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) {
                 jsonWriter.beginObject();
                 jsonWriter.name("edit-id").value(patchStatusEntity.getEditId());
                 if (patchStatusEntity.getEditErrors() != null) {
                     reportErrors(patchStatusEntity.getEditErrors(), jsonWriter);
                 } else {
                     if (patchStatusEntity.isOk()) {
-                        jsonWriter.name("ok").nullValue();
+                        reportSuccess(jsonWriter);
                     }
                 }
                 jsonWriter.endObject();
@@ -82,22 +88,33 @@ public class PATCHJsonBodyWriter implements MessageBodyWriter<PATCHStatusContext
         jsonWriter.endObject();
         jsonWriter.endObject();
         jsonWriter.flush();
+    }
 
+    private void reportSuccess(final JsonWriter jsonWriter) throws IOException {
+        jsonWriter.name("ok").beginArray().nullValue().endArray();
     }
 
-    private static void reportErrors(List<RestconfError> errors, JsonWriter jsonWriter) throws IOException {
+    private static void reportErrors(final List<RestconfError> errors, final JsonWriter jsonWriter) throws IOException {
         jsonWriter.name("errors");
         jsonWriter.beginObject();
         jsonWriter.name("error");
         jsonWriter.beginArray();
 
-        for (RestconfError restconfError : errors) {
+        for (final RestconfError restconfError : errors) {
             jsonWriter.beginObject();
             jsonWriter.name("error-type").value(restconfError.getErrorType().getErrorTypeTag());
             jsonWriter.name("error-tag").value(restconfError.getErrorTag().getTagValue());
-            //TODO: fix error-path reporting (separate error-path from error-message)
-            //jsonWriter.name("error-path").value(restconfError.getErrorPath());
-            jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+
+            // optional node
+            if (restconfError.getErrorPath() != null) {
+                jsonWriter.name("error-path").value(restconfError.getErrorPath().toString());
+            }
+
+            // optional node
+            if (restconfError.getErrorMessage() != null) {
+                jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+            }
+
             jsonWriter.endObject();
         }
 
index 2d094a6439c8782d2cf72ccf1112a57fe64b812d..a4ac36178cccca6280977e4b01f6384a6ab7b4b8 100644 (file)
@@ -5,6 +5,7 @@
  * 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.netconf.sal.rest.impl;
 
 import java.io.IOException;
@@ -22,14 +23,17 @@ import javax.xml.stream.FactoryConfigurationError;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
-import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
 import org.opendaylight.netconf.sal.rest.api.RestconfService;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.restconf.utils.RestconfConstants;
 
 @Provider
-@Produces({ MediaTypes.PATCH_STATUS + RestconfService.XML})
+@Produces({Draft02.MediaTypes.PATCH_STATUS + RestconfService.XML,
+        Draft11.MediaTypes.PATCH_STATUS + RestconfConstants.XML})
 public class PATCHXmlBodyWriter implements MessageBodyWriter<PATCHStatusContext> {
 
     private static final XMLOutputFactory XML_FACTORY;
@@ -40,33 +44,35 @@ public class PATCHXmlBodyWriter implements MessageBodyWriter<PATCHStatusContext>
     }
 
     @Override
-    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+    public boolean isWriteable(final Class<?> type, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
         return type.equals(PATCHStatusContext.class);
     }
 
     @Override
-    public long getSize(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
-            annotations, MediaType mediaType) {
+    public long getSize(final PATCHStatusContext patchStatusContext, Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
         return -1;
     }
 
     @Override
-    public void writeTo(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
-            annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
-                throws IOException, WebApplicationException {
+    public void writeTo(final PATCHStatusContext patchStatusContext, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+            throws IOException, WebApplicationException {
 
         try {
-            XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream);
+            final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream);
             writeDocument(xmlWriter, patchStatusContext);
         } catch (final XMLStreamException e) {
             throw new IllegalStateException(e);
         } catch (final FactoryConfigurationError e) {
             throw new IllegalStateException(e);
         }
-
     }
 
-    private static void writeDocument(XMLStreamWriter writer, PATCHStatusContext context) throws XMLStreamException, IOException {
+    private static void writeDocument(final XMLStreamWriter writer, final PATCHStatusContext context)
+            throws XMLStreamException, IOException {
         writer.writeStartElement("", "yang-patch-status", "urn:ietf:params:xml:ns:yang:ietf-yang-patch");
         writer.writeStartElement("patch-id");
         writer.writeCharacters(context.getPatchId());
@@ -79,7 +85,7 @@ public class PATCHXmlBodyWriter implements MessageBodyWriter<PATCHStatusContext>
                 reportErrors(context.getGlobalErrors(), writer);
             }
             writer.writeStartElement("edit-status");
-            for (PATCHStatusEntity patchStatusEntity : context.getEditCollection()) {
+            for (final PATCHStatusEntity patchStatusEntity : context.getEditCollection()) {
                 writer.writeStartElement("edit");
                 writer.writeStartElement("edit-id");
                 writer.writeCharacters(patchStatusEntity.getEditId());
@@ -101,23 +107,32 @@ public class PATCHXmlBodyWriter implements MessageBodyWriter<PATCHStatusContext>
         writer.flush();
     }
 
-    private static void reportErrors(List<RestconfError> errors, XMLStreamWriter writer) throws IOException, XMLStreamException {
+    private static void reportErrors(final List<RestconfError> errors, final XMLStreamWriter writer)
+            throws IOException, XMLStreamException {
         writer.writeStartElement("errors");
 
-        for (RestconfError restconfError : errors) {
+        for (final RestconfError restconfError : errors) {
             writer.writeStartElement("error-type");
             writer.writeCharacters(restconfError.getErrorType().getErrorTypeTag());
             writer.writeEndElement();
+
             writer.writeStartElement("error-tag");
             writer.writeCharacters(restconfError.getErrorTag().getTagValue());
             writer.writeEndElement();
-            //TODO: fix error-path reporting (separate error-path from error-message)
-//            writer.writeStartElement("error-path");
-//            writer.writeCharacters(restconfError.getErrorPath());
-//            writer.writeEndElement();
-            writer.writeStartElement("error-message");
-            writer.writeCharacters(restconfError.getErrorMessage());
-            writer.writeEndElement();
+
+            // optional node
+            if (restconfError.getErrorPath() != null) {
+                writer.writeStartElement("error-path");
+                writer.writeCharacters(restconfError.getErrorPath().toString());
+                writer.writeEndElement();
+            }
+
+            // optional node
+            if (restconfError.getErrorMessage() != null) {
+                writer.writeStartElement("error-message");
+                writer.writeCharacters(restconfError.getErrorMessage());
+                writer.writeEndElement();
+            }
         }
 
         writer.writeEndElement();
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
new file mode 100644 (file)
index 0000000..a265ea9
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.netconf.sal.rest.impl;
+
+import com.google.common.base.Preconditions;
+import java.net.URI;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * @deprecated This class will be replaced by
+ * {@link org.opendaylight.restconf.utils.patch.Draft11StringModuleInstanceIdentifierCodec}
+ */
+@Deprecated
+final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+
+    private final DataSchemaContextTree dataContextTree;
+    private final SchemaContext context;
+    private final String defaultPrefix;
+
+    StringModuleInstanceIdentifierCodec(SchemaContext context) {
+        this.context = Preconditions.checkNotNull(context);
+        this.dataContextTree = DataSchemaContextTree.from(context);
+        this.defaultPrefix = "";
+    }
+
+    StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+        this.context = Preconditions.checkNotNull(context);
+        this.dataContextTree = DataSchemaContextTree.from(context);
+        this.defaultPrefix = defaultPrefix;
+    }
+
+    @Override
+    protected Module moduleForPrefix(@Nonnull String prefix) {
+        if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
+            return this.context.findModuleByName(this.defaultPrefix, null);
+        } else {
+            return this.context.findModuleByName(prefix, null);
+        }
+    }
+
+    @Nonnull
+    @Override
+    protected DataSchemaContextTree getDataContextTree() {
+        return this.dataContextTree;
+    }
+
+    @Nullable
+    @Override
+    protected String prefixForNamespace(@Nonnull URI namespace) {
+        final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
+        return module == null ? null : module.getName();
+    }
+}
\ No newline at end of file
index fe63f3137338e0d6a8ca1b0d85c1bc919178838b..eb12029af965d7c11d362c19f24739c7fdab5e4f 100644 (file)
@@ -8,14 +8,17 @@
 
 package org.opendaylight.netconf.sal.rest.impl;
 
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
+import java.net.URI;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import javax.annotation.Nonnull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -25,33 +28,42 @@ import javax.ws.rs.ext.Provider;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
 import org.opendaylight.netconf.sal.rest.api.RestconfService;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.restconf.utils.patch.Draft11XmlToPATCHBodyReader;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
+/**
+ * @deprecated This class will be replaced by {@link Draft11XmlToPATCHBodyReader}
+ */
+@Deprecated
 @Provider
-@Consumes({MediaTypes.PATCH + RestconfService.XML})
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.XML})
 public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements
         MessageBodyReader<PATCHContext> {
 
@@ -77,14 +89,16 @@ public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider i
     }
 
     @Override
-    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+    public boolean isReadable(final Class<?> type, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
         return true;
     }
 
     @Override
-    public PATCHContext readFrom(Class<PATCHContext> type, Type genericType, Annotation[] annotations, MediaType
-            mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
-            WebApplicationException {
+    public PATCHContext readFrom(final Class<PATCHContext> type, final Type genericType,
+                                 final Annotation[] annotations, final MediaType mediaType,
+                                 final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+            throws IOException, WebApplicationException {
 
         try {
             final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
@@ -117,47 +131,183 @@ public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider i
         final List<PATCHEntity> resultCollection = new ArrayList<>();
         final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
         final NodeList editNodes = doc.getElementsByTagName("edit");
-        final DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
         final DomToNormalizedNodeParserFactory parserFactory =
                 DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER,
                         pathContext.getSchemaContext());
 
         for (int i = 0; i < editNodes.getLength(); i++) {
-            Element element = (Element) editNodes.item(i);
+            DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
+            final Element element = (Element) editNodes.item(i);
             final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue();
             final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue();
             final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue();
-            DataSchemaNode targetNode = ((DataNodeContainer)(pathContext.getSchemaNode())).getDataChildByName
-                    (target.replace("/", ""));
+            final List<Element> values = readValueNodes(element, operation);
+            final Element firstValueElement = values != null ? values.get(0) : null;
+
+            // get namespace according to schema node from path context or value
+            final String namespace = (firstValueElement == null) ?
+                    schemaNode.getQName().getNamespace().toString() : firstValueElement.getNamespaceURI();
+
+            // find module according to namespace
+            final Module module = pathContext.getSchemaContext().findModuleByNamespace(
+                    URI.create(namespace)).iterator().next();
+
+            // initialize codec + set default prefix derived from module name
+            final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+                    pathContext.getSchemaContext(), module.getName());
+
+            // find complete path to target and target schema node
+            // target can be also empty (only slash)
+            YangInstanceIdentifier targetII;
+            final SchemaNode targetNode;
+            if (target.equals("/")) {
+                targetII = pathContext.getInstanceIdentifier();
+                targetNode = pathContext.getSchemaContext();
+            } else {
+                targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier())
+                        .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement,
+                                namespace, module.getQNameModule().getFormattedRevision())));
+
+                targetNode = SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath().getParent());
+
+                // move schema node
+                schemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath());
+            }
+
             if (targetNode == null) {
                 LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode());
                 throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
                         ErrorTag.MALFORMED_MESSAGE);
             } else {
-                final YangInstanceIdentifier targetII = pathContext.getInstanceIdentifier().node(targetNode.getQName());
-                final NodeList valueNodes = element.getElementsByTagName("value").item(0).getChildNodes();
-                Element value = null;
-                for (int j = 0; j < valueNodes.getLength(); j++) {
-                    if (valueNodes.item(j) instanceof Element) {
-                        value = (Element) valueNodes.item(j);
-                        break;
+                if (PATCHEditOperation.isPatchOperationWithValue(operation)) {
+                    NormalizedNode<?, ?> parsed = null;
+                    if (schemaNode instanceof ContainerSchemaNode) {
+                        parsed = parserFactory.getContainerNodeParser().parse(values, (ContainerSchemaNode) schemaNode);
+                    } else if (schemaNode instanceof ListSchemaNode) {
+                        parsed = parserFactory.getMapNodeParser().parse(values, (ListSchemaNode) schemaNode);
                     }
-                }
-                NormalizedNode<?, ?> parsed = null;
-                if (schemaNode instanceof ContainerSchemaNode) {
-                    parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(value),
-                            (ContainerSchemaNode) targetNode);
-                } else if (schemaNode instanceof ListSchemaNode) {
-                    NormalizedNode<?, ?> parsedValue = parserFactory.getMapEntryNodeParser().parse(Collections
-                            .singletonList(value), (ListSchemaNode) targetNode);
-                    parsed = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(new NodeIdentifier
-                            (targetNode.getQName())).withChild((MapEntryNode) parsedValue).build();
-                }
 
-                resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed));
+                    // for lists allow to manipulate with list items through their parent
+                    if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                        targetII = targetII.getParent();
+                    }
+
+                    resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed));
+                } else {
+                    resultCollection.add(new PATCHEntity(editId, operation, targetII));
+                }
             }
         }
 
         return new PATCHContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
     }
+
+    /**
+     * Read value nodes
+     * @param element Element of current edit operation
+     * @param operation Name of current operation
+     * @return List of value elements
+     */
+    private List<Element> readValueNodes(@Nonnull final Element element, @Nonnull final String operation) {
+        final Node valueNode = element.getElementsByTagName("value").item(0);
+
+        if (PATCHEditOperation.isPatchOperationWithValue(operation) && valueNode == null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (!PATCHEditOperation.isPatchOperationWithValue(operation) && valueNode != null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (valueNode == null) {
+            return null;
+        }
+
+        final List<Element> result = new ArrayList<>();
+        final NodeList childNodes = valueNode.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            if (childNodes.item(i) instanceof Element) {
+                result.add((Element) childNodes.item(i));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare non-conditional XPath suitable for deserialization
+     * with {@link StringModuleInstanceIdentifierCodec}
+     * @param schemaNode Top schema node
+     * @param target Edit operation target
+     * @param value Element with value
+     * @param namespace Module namespace
+     * @param revision Module revision
+     * @return Non-conditional XPath
+     */
+    private String prepareNonCondXpath(@Nonnull final DataSchemaNode schemaNode, @Nonnull final String target,
+                                       @Nonnull final Element value, @Nonnull final String namespace,
+                                       @Nonnull String revision) {
+        final Iterator<String> args = Splitter.on("/").split(target.substring(target.indexOf(':') + 1)).iterator();
+
+        final StringBuilder nonCondXpath = new StringBuilder();
+        SchemaNode childNode = schemaNode;
+
+        while (args.hasNext()) {
+            final String s = args.next();
+            nonCondXpath.append("/");
+            nonCondXpath.append(s);
+            childNode = ((DataNodeContainer) childNode).getDataChildByName(QName.create(namespace, revision, s));
+
+            if (childNode instanceof ListSchemaNode && args.hasNext()) {
+                appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), args);
+            }
+        }
+
+        if (childNode instanceof ListSchemaNode && value != null) {
+            final Iterator<String> keyValues = readKeyValues(value,
+                    ((ListSchemaNode) childNode).getKeyDefinition().iterator());
+            appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), keyValues);
+        }
+
+        return nonCondXpath.toString();
+    }
+
+    /**
+     * Read value for every list key
+     * @param value Value element
+     * @param keys Iterator of list keys names
+     * @return Iterator of list keys values
+     */
+    private Iterator<String> readKeyValues(@Nonnull final Element value, @Nonnull final Iterator<QName> keys) {
+        final List<String> result = new ArrayList<>();
+
+        while (keys.hasNext()) {
+            result.add(value.getElementsByTagName(keys.next().getLocalName()).item(0).getFirstChild().getNodeValue());
+        }
+
+        return result.iterator();
+    }
+
+    /**
+     * Append key name - key value pairs for every list key to {@code nonCondXpath}
+     * @param nonCondXpath Builder for creating non-conditional XPath
+     * @param keyNames Iterator of list keys names
+     * @param keyValues Iterator of list keys values
+     */
+    private void appendKeys(@Nonnull final StringBuilder nonCondXpath, @Nonnull final Iterator<QName> keyNames,
+                            @Nonnull final Iterator<String> keyValues) {
+        while (keyNames.hasNext()) {
+            nonCondXpath.append("[");
+            nonCondXpath.append(keyNames.next().getLocalName());
+            nonCondXpath.append("=");
+            nonCondXpath.append("'");
+            nonCondXpath.append(keyValues.next());
+            nonCondXpath.append("'");
+            nonCondXpath.append("]");
+        }
+    }
 }
index e2097310f2ce485d422941dc05237f665a4a069e..3b888dbcdf3657985c9ae9c26e7a39e1d26fc287 100644 (file)
@@ -12,15 +12,20 @@ import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastor
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.CountDownLatch;
+import javax.annotation.Nullable;
 import javax.ws.rs.core.Response.Status;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
@@ -28,6 +33,8 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
 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.DOMNotificationListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
@@ -35,6 +42,7 @@ import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -54,12 +62,16 @@ public class BrokerFacade {
     private volatile DOMRpcService rpcService;
     private volatile ConsumerSession context;
     private DOMDataBroker domDataBroker;
+    private DOMNotificationService domNotification;
 
-    private BrokerFacade() {
-    }
+    private BrokerFacade() {}
 
     public void setRpcService(final DOMRpcService router) {
-        rpcService = router;
+        this.rpcService = router;
+    }
+
+    public void setDomNotificationService(final DOMNotificationService domNotification) {
+        this.domNotification = domNotification;
     }
 
     public void setContext(final ConsumerSession context) {
@@ -71,7 +83,7 @@ public class BrokerFacade {
     }
 
     private void checkPreconditions() {
-        if (context == null || domDataBroker == null) {
+        if ((this.context == null) || (this.domDataBroker == null)) {
             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
         }
     }
@@ -79,7 +91,7 @@ public class BrokerFacade {
     // READ configuration
     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
         checkPreconditions();
-        return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
+        return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
     }
 
     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
@@ -95,7 +107,7 @@ public class BrokerFacade {
     // READ operational
     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
         checkPreconditions();
-        return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
+        return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
     }
 
     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
@@ -108,19 +120,66 @@ public class BrokerFacade {
         throw new RestconfDocumentedException(errMsg);
     }
 
-    // PUT configuration
-    public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
+    /**
+     * <b>PUT configuration data</b>
+     *
+     * Prepare result(status) for PUT operation and PUT data via transaction.
+     * Return wrapped status and future from PUT.
+     *
+     * @param globalSchema
+     *            - used by merge parents (if contains list)
+     * @param path
+     *            - path of node
+     * @param payload
+     *            - input data
+     * @return wrapper of status and future of PUT
+     */
+    public PutResult commitConfigurationDataPut(
             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
+        Preconditions.checkNotNull(globalSchema);
+        Preconditions.checkNotNull(path);
+        Preconditions.checkNotNull(payload);
+
         checkPreconditions();
-        return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+
+        final DOMDataReadWriteTransaction newReadWriteTransaction = this.domDataBroker.newReadWriteTransaction();
+        final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null ? Status.OK
+                : Status.CREATED;
+        final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
+                newReadWriteTransaction, CONFIGURATION, path, payload, globalSchema);
+        return new PutResult(status, future);
     }
 
-    public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
+    /**
+     * <b>PUT configuration data (Mount point)</b>
+     *
+     * Prepare result(status) for PUT operation and PUT data via transaction.
+     * Return wrapped status and future from PUT.
+     *
+     * @param mountPoint
+     *            - mount point for getting transaction for operation and schema
+     *            context for merging parents(if contains list)
+     * @param path
+     *            - path of node
+     * @param payload
+     *            - input data
+     * @return wrapper of status and future of PUT
+     */
+    public PutResult commitMountPointDataPut(
             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
+        Preconditions.checkNotNull(mountPoint);
+        Preconditions.checkNotNull(path);
+        Preconditions.checkNotNull(payload);
+
         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
         if (domDataBrokerService.isPresent()) {
-            return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
+            final DOMDataReadWriteTransaction newReadWriteTransaction = domDataBrokerService.get().newReadWriteTransaction();
+            final Status status = readDataViaTransaction(newReadWriteTransaction, CONFIGURATION, path) != null
+                    ? Status.OK : Status.CREATED;
+            final CheckedFuture<Void, TransactionCommitFailedException> future = putDataViaTransaction(
+                    newReadWriteTransaction, CONFIGURATION, path,
                     payload, mountPoint.getSchemaContext());
+            return new PutResult(status, future);
         }
         final String errMsg = "DOM data broker service isn't available for mount point " + path;
         LOG.warn(errMsg);
@@ -128,14 +187,15 @@ public class BrokerFacade {
     }
 
     public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
-                                                                      final SchemaContext globalSchema) {
-        final DOMDataReadWriteTransaction patchTransaction = domDataBroker.newReadWriteTransaction();
-        List<PATCHStatusEntity> editCollection = new ArrayList<>();
+                                                                      final SchemaContext globalSchema)
+            throws InterruptedException {
+        final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
+        final List<PATCHStatusEntity> editCollection = new ArrayList<>();
+
         List<RestconfError> editErrors;
-        List<RestconfError> globalErrors = null;
         int errorCounter = 0;
 
-        for (PATCHEntity patchEntity : context.getData()) {
+        for (final PATCHEntity patchEntity : context.getData()) {
             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
 
             switch (operation) {
@@ -145,7 +205,7 @@ public class BrokerFacade {
                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
                                     patchEntity.getNode(), globalSchema);
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
-                        } catch (RestconfDocumentedException e) {
+                        } catch (final RestconfDocumentedException e) {
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
@@ -159,7 +219,7 @@ public class BrokerFacade {
                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
                                     .getTargetNode(), patchEntity.getNode(), globalSchema);
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
-                        } catch (RestconfDocumentedException e) {
+                        } catch (final RestconfDocumentedException e) {
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
@@ -173,7 +233,7 @@ public class BrokerFacade {
                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
                                     .getTargetNode());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
-                        } catch (RestconfDocumentedException e) {
+                        } catch (final RestconfDocumentedException e) {
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
@@ -187,33 +247,67 @@ public class BrokerFacade {
                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
                                     .getTargetNode());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
-                        } catch (RestconfDocumentedException e) {
+                        } catch (final RestconfDocumentedException e) {
                             LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
                                     patchEntity.getEditId(), e);
                         }
                     }
                     break;
+                case MERGE:
+                    if (errorCounter == 0) {
+                        try {
+                            mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+                                    patchEntity.getNode(), globalSchema);
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editErrors = new ArrayList<>();
+                            editErrors.addAll(e.getErrors());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+                            errorCounter++;
+                        }
+                    }
+                    break;
             }
         }
 
-        //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
-        //globalErrors = new ArrayList<>();
-        if (errorCounter == 0) {
-            final CheckedFuture<Void, TransactionCommitFailedException> submit = patchTransaction.submit();
-            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
-                    globalErrors);
-        } else {
+        // if errors then cancel transaction and return error status
+        if (errorCounter != 0) {
             patchTransaction.cancel();
-            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
-                    globalErrors);
+            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
         }
+
+        // if no errors commit transaction
+        final CountDownLatch waiter = new CountDownLatch(1);
+        final CheckedFuture<Void, TransactionCommitFailedException> future = patchTransaction.submit();
+        final PATCHStatusContextHelper status = new PATCHStatusContextHelper();
+
+        Futures.addCallback(future, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(@Nullable final Void result) {
+                status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
+                        true, null));
+                waiter.countDown();
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                // if commit failed it is global error
+                status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
+                        false, Lists.newArrayList(
+                        new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
+                waiter.countDown();
+            }
+        });
+
+        waiter.await();
+        return status.getStatus();
     }
 
     // POST configuration
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
         checkPreconditions();
-        return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+        return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
     }
 
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
@@ -232,14 +326,14 @@ public class BrokerFacade {
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
             final YangInstanceIdentifier path) {
         checkPreconditions();
-        return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
+        return deleteDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path);
     }
 
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
         if (domDataBrokerService.isPresent()) {
-            return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
+            return deleteDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path);
         }
         final String errMsg = "DOM data broker service isn't available for mount point " + path;
         LOG.warn(errMsg);
@@ -249,11 +343,11 @@ public class BrokerFacade {
     // RPC
     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
         checkPreconditions();
-        if (rpcService == null) {
+        if (this.rpcService == null) {
             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
         }
         LOG.trace("Invoke RPC {} with input: {}", type, input);
-        return rpcService.invokeRpc(type, input);
+        return this.rpcService.invokeRpc(type, input);
     }
 
     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
@@ -265,7 +359,7 @@ public class BrokerFacade {
         }
 
         final YangInstanceIdentifier path = listener.getPath();
-        final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
+        final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
                 datastore, path, listener, scope);
 
         listener.setRegistration(registration);
@@ -275,23 +369,32 @@ public class BrokerFacade {
             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
-        if (listenableFuture != null) {
-            Optional<NormalizedNode<?, ?>> optional;
-            try {
-                LOG.debug("Reading result data from transaction.");
-                optional = listenableFuture.get();
-            } catch (InterruptedException | ExecutionException e) {
-                LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
-                throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
+        final ReadDataResult<NormalizedNode<?, ?>> readData = new ReadDataResult<>();
+        final CountDownLatch responseWaiter = new CountDownLatch(1);
 
+        Futures.addCallback(listenableFuture, new FutureCallback<Optional<NormalizedNode<?, ?>>>() {
+
+            @Override
+            public void onSuccess(final Optional<NormalizedNode<?, ?>> result) {
+                responseWaiter.countDown();
+                handlingCallback(null, datastore, path, result, readData);
             }
-            if (optional != null) {
-                if (optional.isPresent()) {
-                    return optional.get();
-                }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                responseWaiter.countDown();
+                handlingCallback(t, datastore, path, null, null);
             }
+        });
+
+        try {
+            responseWaiter.await();
+        } catch (final InterruptedException e) {
+            final String msg = "Problem while waiting for response";
+            LOG.warn(msg);
+            throw new RestconfDocumentedException(msg, e);
         }
-        return null;
+        return readData.getResult();
     }
 
     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
@@ -339,20 +442,90 @@ public class BrokerFacade {
         }
     }
 
-    private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-        final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
+    /**
+     * Check if item already exists. Throws error if it does NOT already exist.
+     * @param rWTransaction Current transaction
+     * @param store Used datastore
+     * @param path Path to item to verify its existence
+     */
+    private void checkItemExists(final DOMDataReadWriteTransaction rWTransaction,
+                                 final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+        final CountDownLatch responseWaiter = new CountDownLatch(1);
+        final ReadDataResult<Boolean> readData = new ReadDataResult<>();
+        final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
+
+        Futures.addCallback(future, new FutureCallback<Boolean>() {
+            @Override
+            public void onSuccess(@Nullable final Boolean result) {
+                responseWaiter.countDown();
+                handlingCallback(null, store, path, Optional.of(result), readData);
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                responseWaiter.countDown();
+                handlingCallback(t, store, path, null, null);
+            }
+        });
+
         try {
-            if (futureDatastoreData.get()) {
-                final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
-                LOG.trace("{}:{}", errMsg, path);
-                rWTransaction.cancel();
-                throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
-                        ErrorTag.DATA_EXISTS);
+            responseWaiter.await();
+        } catch (final InterruptedException e) {
+            final String msg = "Problem while waiting for response";
+            LOG.warn(msg);
+            throw new RestconfDocumentedException(msg, e);
+        }
+
+        if (!readData.getResult()) {
+            final String errMsg = "Operation via Restconf was not executed because data does not exist";
+            LOG.trace("{}:{}", errMsg, path);
+            rWTransaction.cancel();
+            throw new RestconfDocumentedException("Data does not exist for path: " + path, ErrorType.PROTOCOL,
+                    ErrorTag.DATA_MISSING);
+        }
+    }
+
+    /**
+     * Check if item does NOT already exist. Throws error if it already exists.
+     * @param rWTransaction Current transaction
+     * @param store Used datastore
+     * @param path Path to item to verify its existence
+     */
+    private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,
+                                        final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+        final CountDownLatch responseWaiter = new CountDownLatch(1);
+        final ReadDataResult<Boolean> readData = new ReadDataResult<>();
+        final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
+
+        Futures.addCallback(future, new FutureCallback<Boolean>() {
+            @Override
+            public void onSuccess(@Nullable final Boolean result) {
+                responseWaiter.countDown();
+                handlingCallback(null, store, path, Optional.of(result), readData);
             }
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
+
+            @Override
+            public void onFailure(final Throwable t) {
+                responseWaiter.countDown();
+                handlingCallback(t, store, path, null, null);
+            }
+        });
+
+        try {
+            responseWaiter.await();
+        } catch (final InterruptedException e) {
+            final String msg = "Problem while waiting for response";
+            LOG.warn(msg);
+            throw new RestconfDocumentedException(msg, e);
         }
 
+        if (readData.getResult()) {
+            final String errMsg = "Operation via Restconf was not executed because data already exists";
+            LOG.trace("{}:{}", errMsg, path);
+            rWTransaction.cancel();
+            throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
+                    ErrorTag.DATA_EXISTS);
+        }
     }
 
     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
@@ -373,11 +546,12 @@ public class BrokerFacade {
     }
 
     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
-            final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
             final YangInstanceIdentifier path) {
         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
-        writeTransaction.delete(datastore, path);
-        return writeTransaction.submit();
+        checkItemExists(readWriteTransaction, datastore, path);
+        readWriteTransaction.delete(datastore, path);
+        return readWriteTransaction.submit();
     }
 
     private void deleteDataWithinTransaction(
@@ -387,38 +561,117 @@ public class BrokerFacade {
         writeTransaction.delete(datastore, path);
     }
 
+    private void mergeDataWithinTransaction(
+            final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+        LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+        ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+
+        // merging is necessary only for lists otherwise we can call put method
+        if (payload instanceof MapNode) {
+            writeTransaction.merge(datastore, path, payload);
+        } else {
+            writeTransaction.put(datastore, path, payload);
+        }
+    }
+
     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
         this.domDataBroker = domDataBroker;
     }
 
-    private void ensureParentsByMerge(final LogicalDatastoreType store,
-                                      final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
+    /**
+     * Helper class for result of transaction commit callback.
+     * @param <T> Type of result
+     */
+    private final class ReadDataResult<T> {
+        T result = null;
+
+        T getResult() {
+            return this.result;
+        }
+
+        void setResult(final T result) {
+            this.result = result;
+        }
+    }
+
+    /**
+     * Set result from transaction commit callback.
+     * @param t Throwable if transaction commit failed
+     * @param datastore Datastore from which data are read
+     * @param path Path from which data are read
+     * @param result Result of read from {@code datastore}
+     * @param readData Result value which will be set
+     * @param <X> Result type
+     */
+    protected final static <X> void handlingCallback(final Throwable t, final LogicalDatastoreType datastore,
+                                                     final YangInstanceIdentifier path, final Optional<X> result,
+                                                     final ReadDataResult<X> readData) {
+        if (t != null) {
+            LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, t);
+            throw new RestconfDocumentedException("Problem to get data from transaction.", t);
+        } else {
+            LOG.debug("Reading result data from transaction.");
+            if (result != null) {
+                if (result.isPresent()) {
+                    readData.setResult(result.get());
+                }
+            }
+        }
+    }
+
+    public void registerToListenNotification(final NotificationListenerAdapter listener) {
+        checkPreconditions();
+
+        if (listener.isListening()) {
+            return;
+        }
+
+        final SchemaPath path = listener.getSchemaPath();
+        final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
+                .registerNotificationListener(listener, path);
+
+        listener.setRegistration(registration);
+    }
+
+    private final class PATCHStatusContextHelper {
+        PATCHStatusContext status;
+
+        public PATCHStatusContext getStatus() {
+            return this.status;
+        }
+
+        public void setStatus(final PATCHStatusContext status) {
+            this.status = status;
+        }
+    }
+
+    private void ensureParentsByMerge(final LogicalDatastoreType store, final YangInstanceIdentifier normalizedPath,
+            final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
         YangInstanceIdentifier rootNormalizedPath = null;
 
         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
 
-        while(it.hasNext()) {
+        while (it.hasNext()) {
             final PathArgument pathArgument = it.next();
-            if(rootNormalizedPath == null) {
+            if (rootNormalizedPath == null) {
                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
             }
 
-            // Skip last element, its not a parent
-            if(it.hasNext()) {
+            if (it.hasNext()) {
                 normalizedPathWithoutChildArgs.add(pathArgument);
             }
         }
 
-        // No parent structure involved, no need to ensure parents
-        if(normalizedPathWithoutChildArgs.isEmpty()) {
+        if (normalizedPathWithoutChildArgs.isEmpty()) {
             return;
         }
 
         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
 
-        final NormalizedNode<?, ?> parentStructure =
-                ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
+        final NormalizedNode<?, ?> parentStructure = ImmutableNodes.fromInstanceId(schemaContext,
+                YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
         rwTx.merge(store, rootNormalizedPath, parentStructure);
     }
 }
index e0bdfd5893863848c2a991e0449f904b014a8d38..da98193f230090aec00010cb013adae2a9944804 100644 (file)
@@ -8,6 +8,8 @@
 
 package org.opendaylight.netconf.sal.restconf.impl;
 
+import javax.annotation.Nonnull;
+
 /**
  *
  * Each YANG patch edit specifies one edit operation on the target data
@@ -15,12 +17,32 @@ package org.opendaylight.netconf.sal.restconf.impl;
  * operations, but also includes some new operations.
  *
  */
-enum PATCHEditOperation {
+public enum PATCHEditOperation {
     CREATE,  //post
     DELETE,  //delete
     INSERT,  //post
     MERGE,
     MOVE,    //delete+post
     REPLACE, //put
-    REMOVE   //delete
-}
+    REMOVE;  //delete
+
+    /**
+     * Not all patch operations support value node. Check if operation requires value or not.
+     * @param operation Name of the operation to be checked
+     * @return true if operation requires value, false otherwise
+     */
+    public static final boolean isPatchOperationWithValue(@Nonnull final String operation) {
+        switch (PATCHEditOperation.valueOf(operation.toUpperCase())) {
+            case CREATE:
+                // fall through
+            case MERGE:
+                // fall through
+            case REPLACE:
+                // fall through
+            case INSERT:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
\ No newline at end of file
index ae16edc9d46b63d244031d94c2c2103678c37cdd..b857c8f82e71b0ab699360a46c3ac5df509ec6c0 100644 (file)
@@ -19,6 +19,13 @@ public class PATCHEntity {
     private final YangInstanceIdentifier targetNode;
     private final NormalizedNode<?,?> node;
 
+    /**
+     * Constructor to create PATCHEntity for PATCH operations which require value leaf representing data to be present.
+     * @param editId Id of PATCH edit
+     * @param operation PATCH edit operation
+     * @param targetNode Target node for PATCH edit operation
+     * @param node Data defined by value leaf used by edit operation
+     */
     public PATCHEntity(final String editId, final String operation, final YangInstanceIdentifier targetNode, final
     NormalizedNode<?, ?> node) {
         this.editId = Preconditions.checkNotNull(editId);
@@ -27,6 +34,20 @@ public class PATCHEntity {
         this.node = Preconditions.checkNotNull(node);
     }
 
+    /**
+     * Constructor to create PATCHEntity for PATCH operations which do not allow value leaf representing data to be
+     * present. <code>node</code> is set to <code>null</code> meaning that data are not allowed for edit operation.
+     * @param editId Id of PATCH edit
+     * @param operation PATCH edit operation
+     * @param targetNode Target node for PATCH edit operation
+     */
+    public PATCHEntity(final String editId, final String operation, final YangInstanceIdentifier targetNode) {
+        this.editId = Preconditions.checkNotNull(editId);
+        this.operation = Preconditions.checkNotNull(operation);
+        this.targetNode = Preconditions.checkNotNull(targetNode);
+        this.node = null;
+    }
+
     public String getOperation() {
         return operation;
     }
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/PutResult.java
new file mode 100644 (file)
index 0000000..befd2ce
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.netconf.sal.restconf.impl;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import javax.ws.rs.core.Response.Status;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+
+/**
+ * Wrapper for status and future of PUT operation.
+ *
+ */
+public class PutResult {
+    private final Status status;
+    private final CheckedFuture<Void, TransactionCommitFailedException> future;
+
+    /**
+     * Wrap status and future by constructor - make this immutable
+     *
+     * @param status
+     *            - status of operations
+     * @param future
+     *            - result of submit of PUT operation
+     */
+    public PutResult(final Status status, final CheckedFuture<Void, TransactionCommitFailedException> future) {
+        this.status = status;
+        this.future = future;
+    }
+
+    /**
+     * Get status
+     *
+     * @return {@link Status} result
+     */
+    public Status getStatus() {
+        return this.status;
+    }
+
+    /**
+     * Get future.
+     *
+     * @return {@link CheckedFuture} result
+     */
+    public CheckedFuture<Void, TransactionCommitFailedException> getFutureOfPutData() {
+        return this.future;
+    }
+}
index da3672a2b4334297cabd07d9b8b362cb20f2f739..0e21d6f548a284b503acaf2d5c938233448b9c8c 100644 (file)
@@ -19,6 +19,7 @@ import javax.ws.rs.core.Response.Status;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
 /**
  * Unchecked exception to communicate error information, as defined in the ietf restcong draft, to be sent to the
@@ -42,7 +43,7 @@ public class RestconfDocumentedException extends WebApplicationException {
      * @param message
      *            A string which provides a plain text string describing the error.
      */
-    public RestconfDocumentedException(String message) {
+    public RestconfDocumentedException(final String message) {
         this(message, RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED);
     }
 
@@ -58,8 +59,10 @@ public class RestconfDocumentedException extends WebApplicationException {
      * @param cause
      *            The underlying exception cause.
      */
-    public RestconfDocumentedException(String message, ErrorType errorType, ErrorTag errorTag, Throwable cause) {
-        this(cause, new RestconfError(errorType, errorTag, message, null, Throwables.getStackTraceAsString(cause)));
+    public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
+                                       final Throwable cause) {
+        this(cause, new RestconfError(errorType, errorTag, message, null,
+                Throwables.getStackTraceAsString(cause), null));
     }
 
     /**
@@ -72,10 +75,27 @@ public class RestconfDocumentedException extends WebApplicationException {
      * @param errorTag
      *            The enumerated tag representing a more specific error cause.
      */
-    public RestconfDocumentedException(String message, ErrorType errorType, ErrorTag errorTag) {
+    public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag) {
         this(null, new RestconfError(errorType, errorTag, message));
     }
 
+    /**
+     * Constructs an instance with an error message, error type, error tag and error path.
+     *
+     * @param message
+     *            A string which provides a plain text string describing the error.
+     * @param errorType
+     *            The enumerated type indicating the layer where the error occurred.
+     * @param errorTag
+     *            The enumerated tag representing a more specific error cause.
+     * @param errorPath
+     *            The instance identifier representing error path
+     */
+    public RestconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
+                                       final YangInstanceIdentifier errorPath) {
+        this(null, new RestconfError(errorType, errorTag, message, errorPath));
+    }
+
     /**
      * Constructs an instance with an error message and exception cause.
      * The stack trace of the exception is included in the error info.
@@ -85,22 +105,22 @@ public class RestconfDocumentedException extends WebApplicationException {
      * @param cause
      *            The underlying exception cause.
      */
-    public RestconfDocumentedException(String message, Throwable cause) {
+    public RestconfDocumentedException(final String message, final Throwable cause) {
         this(cause, new RestconfError(RestconfError.ErrorType.APPLICATION, RestconfError.ErrorTag.OPERATION_FAILED,
-                message, null, Throwables.getStackTraceAsString(cause)));
+                message, null, Throwables.getStackTraceAsString(cause), null));
     }
 
     /**
      * Constructs an instance with the given error.
      */
-    public RestconfDocumentedException(RestconfError error) {
+    public RestconfDocumentedException(final RestconfError error) {
         this(null, error);
     }
 
     /**
      * Constructs an instance with the given errors.
      */
-    public RestconfDocumentedException(String message, Throwable cause, List<RestconfError> errors) {
+    public RestconfDocumentedException(final String message, final Throwable cause, final List<RestconfError> errors) {
         // FIXME: We override getMessage so supplied message is lost for any public access
         // this was lost also in original code.
         super(cause);
@@ -117,7 +137,8 @@ public class RestconfDocumentedException extends WebApplicationException {
     /**
      * Constructs an instance with the given RpcErrors.
      */
-    public RestconfDocumentedException(String message, Throwable cause, Collection<RpcError> rpcErrors) {
+    public RestconfDocumentedException(final String message, final Throwable cause,
+                                       final Collection<RpcError> rpcErrors) {
         this(message, cause, convertToRestconfErrors(rpcErrors));
     }
 
@@ -127,21 +148,21 @@ public class RestconfDocumentedException extends WebApplicationException {
      * @param status
      *            the HTTP status.
      */
-    public RestconfDocumentedException(Status status) {
+    public RestconfDocumentedException(final Status status) {
         Preconditions.checkNotNull(status, "Status can't be null");
         errors = ImmutableList.of();
         this.status = status;
     }
 
-    private RestconfDocumentedException(Throwable cause, RestconfError error) {
+    private RestconfDocumentedException(final Throwable cause, final RestconfError error) {
         super(cause);
         Preconditions.checkNotNull(error, "RestconfError can't be null");
         errors = ImmutableList.of(error);
         status = null;
     }
 
-    private static List<RestconfError> convertToRestconfErrors(Collection<RpcError> rpcErrors) {
-        List<RestconfError> errorList = Lists.newArrayList();
+    private static List<RestconfError> convertToRestconfErrors(final Collection<RpcError> rpcErrors) {
+        final List<RestconfError> errorList = Lists.newArrayList();
         if(rpcErrors != null) {
             for (RpcError rpcError : rpcErrors) {
                 errorList.add(new RestconfError(rpcError));
@@ -151,7 +172,6 @@ public class RestconfDocumentedException extends WebApplicationException {
         return errorList;
     }
 
-
     public List<RestconfError> getErrors() {
         return errors;
     }
index 316e766bd34bfa3d18dfc80ad041342a8cbcf59a..f15515665c28d3e424eaf9efb6bd141e71a1ce0f 100644 (file)
@@ -5,11 +5,13 @@
  * 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.netconf.sal.restconf.impl;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
 import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
 /**
  * Encapsulates a restconf error as defined in the ietf restconf draft.
@@ -23,7 +25,7 @@ import org.opendaylight.yangtools.yang.common.RpcError;
  */
 public class RestconfError {
 
-    public static enum ErrorType {
+    public enum ErrorType {
         /** Errors relating to the transport layer */
         TRANSPORT,
         /** Errors relating to the RPC or notification layer */
@@ -37,7 +39,7 @@ public class RestconfError {
             return name().toLowerCase();
         }
 
-        public static ErrorType valueOfCaseInsensitive(String value) {
+        public static ErrorType valueOfCaseInsensitive(final String value) {
             try {
                 return ErrorType.valueOf(ErrorType.class, value.toUpperCase());
             } catch (IllegalArgumentException e) {
@@ -46,7 +48,7 @@ public class RestconfError {
         }
     }
 
-    public static enum ErrorTag {
+    public enum ErrorTag {
         IN_USE("in-use", 409 /* Conflict */),
         INVALID_VALUE("invalid-value", 400 /* Bad Request */),
         TOO_BIG("too-big", 413 /* Request Entity Too Large */),
@@ -80,7 +82,7 @@ public class RestconfError {
             return this.tagValue.toLowerCase();
         }
 
-        public static ErrorTag valueOfCaseInsensitive(String value) {
+        public static ErrorTag valueOfCaseInsensitive(final String value) {
             try {
                 return ErrorTag.valueOf(ErrorTag.class, value.toUpperCase().replaceAll("-", "_"));
             } catch (IllegalArgumentException e) {
@@ -98,7 +100,7 @@ public class RestconfError {
     private final String errorInfo;
     private final String errorAppTag;
     private final String errorMessage;
-    // TODO: Add in the error-path concept as defined in the ietf draft.
+    private final YangInstanceIdentifier errorPath;
 
     /**
      * Constructs a RestConfError
@@ -110,8 +112,42 @@ public class RestconfError {
      * @param errorMessage
      *            A string which provides a plain text string describing the error.
      */
-    public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage) {
-        this(errorType, errorTag, errorMessage, null);
+    public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage) {
+        this(errorType, errorTag, errorMessage, null, null, null);
+    }
+
+    /**
+     * Constructs a RestConfError object.
+     *
+     * @param errorType
+     *            The enumerated type indicating the layer where the error occurred.
+     * @param errorTag
+     *            The enumerated tag representing a more specific error cause.
+     * @param errorMessage
+     *            A string which provides a plain text string describing the error.
+     * @param errorAppTag
+     *            A string which represents an application-specific error tag that further specifies the error cause.
+     */
+    public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+                         final String errorAppTag) {
+        this(errorType, errorTag, errorMessage, errorAppTag, null, null);
+    }
+
+    /**
+     * Constructs a RestConfError object.
+     *
+     * @param errorType
+     *            The enumerated type indicating the layer where the error occurred.
+     * @param errorTag
+     *            The enumerated tag representing a more specific error cause.
+     * @param errorMessage
+     *            A string which provides a plain text string describing the error.
+     * @param errorPath
+     *            An instance identifier which contains error path
+     */
+    public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+                         final YangInstanceIdentifier errorPath) {
+        this(errorType, errorTag, errorMessage, null, null, errorPath);
     }
 
     /**
@@ -125,9 +161,12 @@ public class RestconfError {
      *            A string which provides a plain text string describing the error.
      * @param errorAppTag
      *            A string which represents an application-specific error tag that further specifies the error cause.
+     * @param errorInfo
+     *            A string, <b>formatted as XML</b>, which contains additional error information.
      */
-    public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage, String errorAppTag) {
-        this(errorType, errorTag, errorMessage, errorAppTag, null);
+    public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+                         final String errorAppTag, final String errorInfo) {
+        this(errorType, errorTag, errorMessage, errorAppTag, errorInfo, null);
     }
 
     /**
@@ -143,9 +182,11 @@ public class RestconfError {
      *            A string which represents an application-specific error tag that further specifies the error cause.
      * @param errorInfo
      *            A string, <b>formatted as XML</b>, which contains additional error information.
+     * @param errorPath
+     *            An instance identifier which contains error path
      */
-    public RestconfError(ErrorType errorType, ErrorTag errorTag, String errorMessage, String errorAppTag,
-            String errorInfo) {
+    public RestconfError(final ErrorType errorType, final ErrorTag errorTag, final String errorMessage,
+                         final String errorAppTag, final String errorInfo, final YangInstanceIdentifier errorPath) {
         Preconditions.checkNotNull(errorType, "Error type is required for RestConfError");
         Preconditions.checkNotNull(errorTag, "Error tag is required for RestConfError");
         this.errorType = errorType;
@@ -153,12 +194,13 @@ public class RestconfError {
         this.errorMessage = errorMessage;
         this.errorAppTag = errorAppTag;
         this.errorInfo = errorInfo;
+        this.errorPath = errorPath;
     }
 
     /**
      * Constructs a RestConfError object from an RpcError.
      */
-    public RestconfError(RpcError rpcError) {
+    public RestconfError(final RpcError rpcError) {
 
         this.errorType = rpcError.getErrorType() == null ? ErrorType.APPLICATION : ErrorType
                 .valueOfCaseInsensitive(rpcError.getErrorType().name());
@@ -181,6 +223,7 @@ public class RestconfError {
         }
 
         this.errorInfo = errorInfo;
+        this.errorPath = null;
     }
 
     public ErrorType getErrorType() {
@@ -203,12 +246,18 @@ public class RestconfError {
         return errorMessage;
     }
 
+    public YangInstanceIdentifier getErrorPath() {
+        return errorPath;
+    }
+
     @Override
     public String toString() {
-        return "error-type: " + errorType.getErrorTypeTag() + ", error-tag: " + errorTag.getTagValue() + ", "
-                + (errorAppTag != null ? "error-app-tag: " + errorAppTag + ", " : "")
-                + (errorMessage != null ? "error-message: " + errorMessage : "")
-                + (errorInfo != null ? "error-info: " + errorInfo + ", " : "") + "]";
+        return "RestconfError ["
+                + "error-type: " + errorType.getErrorTypeTag() + ", error-tag: " + errorTag.getTagValue()
+                + (errorAppTag != null ? ", error-app-tag: " + errorAppTag : "")
+                + (errorMessage != null ? ", error-message: " + errorMessage : "")
+                + (errorInfo != null ? ", error-info: " + errorInfo : "")
+                + (errorPath != null ? ", error-path: " + errorPath.toString() : "")
+                + "]";
     }
-
 }
index 493dbb99c433f2f1d510f5221f12f25b518d1fae..be2e5edd6e29978c26e00a5a70227ead01e76469 100644 (file)
@@ -11,28 +11,31 @@ package org.opendaylight.netconf.sal.restconf.impl;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
-import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import java.math.BigInteger;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
@@ -56,6 +59,7 @@ import org.opendaylight.netconf.sal.rest.api.RestconfService;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -68,10 +72,10 @@ import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 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.LeafSetEntryNode;
+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.api.schema.tree.ModifiedNodeDoesNotExistException;
 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;
@@ -86,6 +90,7 @@ import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
@@ -134,6 +139,12 @@ public class RestconfImpl implements RestconfService {
 
     private static final YangInstanceIdentifier.AugmentationIdentifier SAL_REMOTE_AUG_IDENTIFIER;
 
+    public static final CharSequence DATA_SUBSCR = "data-change-event-subscription";
+    private static final CharSequence CREATE_DATA_SUBSCR = "create-" + DATA_SUBSCR;
+
+    public static final CharSequence NOTIFICATION_STREAM = "notification-stream";
+    private static final CharSequence CREATE_NOTIFICATION_STREAM = "create-" + NOTIFICATION_STREAM;
+
     static {
         try {
             final Date eventSubscriptionAugRevision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-07-08");
@@ -378,7 +389,15 @@ public class RestconfImpl implements RestconfService {
             response = mountRpcServices.get().invokeRpc(type, payload.getData());
         } else {
             if (namespace.toString().equals(SAL_REMOTE_NAMESPACE)) {
-                response = invokeSalRemoteRpcSubscribeRPC(payload);
+                if (identifier.contains(CREATE_DATA_SUBSCR)) {
+                    response = invokeSalRemoteRpcSubscribeRPC(payload);
+                } else if (identifier.contains(CREATE_NOTIFICATION_STREAM)) {
+                    response = invokeSalRemoteRpcNotifiStrRPC(payload);
+                } else {
+                    final String msg = "Not supported operation";
+                    LOG.warn(msg);
+                    throw new RestconfDocumentedException(msg, ErrorType.RPC, ErrorTag.OPERATION_NOT_SUPPORTED);
+                }
             } else {
                 response = this.broker.invokeRpc(type, payload.getData());
             }
@@ -468,9 +487,10 @@ public class RestconfImpl implements RestconfService {
         }
 
         final YangInstanceIdentifier pathIdentifier = ((YangInstanceIdentifier) pathValue);
-        String streamName = null;
+        String streamName = (String) CREATE_DATA_SUBSCR;
         if (!pathIdentifier.isEmpty()) {
-            final String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
+            final String fullRestconfIdentifier = DATA_SUBSCR
+                    + this.controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
 
             LogicalDatastoreType datastore = parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
             datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
@@ -651,36 +671,67 @@ public class RestconfImpl implements RestconfService {
          * failures back to the client and forcing them to handle it via retry (and having to
          * document the behavior).
          */
-        int tries = 2;
+        PutResult result = null;
+        final TryOfPutData tryPutData = new TryOfPutData();
         while(true) {
-            try {
-                if (mountPoint != null) {
-                    this.broker.commitConfigurationDataPut(mountPoint, normalizedII, payload.getData()).checkedGet();
-                } else {
-                    this.broker.commitConfigurationDataPut(this.controllerContext.getGlobalSchema(), normalizedII, payload.getData()).checkedGet();
+            if (mountPoint != null) {
+
+                result = this.broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData());
+            } else {
+                result = this.broker.commitConfigurationDataPut(this.controllerContext.getGlobalSchema(), normalizedII,
+                        payload.getData());
+            }
+            final CountDownLatch waiter = new CountDownLatch(1);
+            Futures.addCallback(result.getFutureOfPutData(), new FutureCallback<Void>() {
+
+                @Override
+                public void onSuccess(final Void result) {
+                    handlingLoggerPut(null, tryPutData, identifier);
+                    waiter.countDown();
+                }
+
+                @Override
+                public void onFailure(final Throwable t) {
+                    waiter.countDown();
+                    handlingLoggerPut(t, tryPutData, identifier);
                 }
+            });
 
+            try {
+                waiter.await();
+            } catch (final InterruptedException e) {
+                final String msg = "Problem while waiting for response";
+                LOG.warn(msg);
+                throw new RestconfDocumentedException(msg, e);
+            }
+
+            if(tryPutData.isDone()){
                 break;
-            } catch (final TransactionCommitFailedException e) {
-                if(e instanceof OptimisticLockFailedException) {
-                    if(--tries <= 0) {
-                        LOG.debug("Got OptimisticLockFailedException on last try - failing " + identifier);
-                        throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
-                    }
+            } else {
+                throw new RestconfDocumentedException("Problem while PUT operations");
+            }
+        }
 
-                    LOG.debug("Got OptimisticLockFailedException - trying again " + identifier);
-                } else {
-                    LOG.debug("Update ConfigDataStore fail " + identifier, e);
-                    throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
+        return Response.status(result.getStatus()).build();
+    }
+
+    protected void handlingLoggerPut(final Throwable t, final TryOfPutData tryPutData, final String identifier) {
+        if (t != null) {
+            if (t instanceof OptimisticLockFailedException) {
+                if (tryPutData.countGet() <= 0) {
+                    LOG.debug("Got OptimisticLockFailedException on last try - failing " + identifier);
+                    throw new RestconfDocumentedException(t.getMessage(), t);
                 }
-            } catch (final Exception e) {
-                final String errMsg = "Error updating data ";
-                LOG.debug(errMsg + identifier, e);
-                throw new RestconfDocumentedException(errMsg, e);
+                LOG.debug("Got OptimisticLockFailedException - trying again " + identifier);
+                tryPutData.countDown();
+            } else {
+                LOG.debug("Update ConfigDataStore fail " + identifier, t);
+                throw new RestconfDocumentedException(t.getMessage(), t);
             }
+        } else {
+            LOG.trace("PUT Successful " + identifier);
+            tryPutData.done();
         }
-
-        return Response.status(Status.OK).build();
     }
 
     private static void validateTopLevelNodeName(final NormalizedNodeContext node,
@@ -801,18 +852,37 @@ public class RestconfImpl implements RestconfService {
         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
         final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
-        try {
-            if (mountPoint != null) {
-                this.broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData()).checkedGet();
-            } else {
-                this.broker.commitConfigurationDataPost(this.controllerContext.getGlobalSchema(), normalizedII, payload.getData()).checkedGet();
+
+        CheckedFuture<Void, TransactionCommitFailedException> future;
+        if (mountPoint != null) {
+            future = this.broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData());
+        } else {
+            future = this.broker.commitConfigurationDataPost(this.controllerContext.getGlobalSchema(), normalizedII,
+                    payload.getData());
+        }
+
+        final CountDownLatch waiter = new CountDownLatch(1);
+        Futures.addCallback(future, new FutureCallback<Void>() {
+
+            @Override
+            public void onSuccess(final Void result) {
+                handlerLoggerPost(null, uriInfo);
+                waiter.countDown();
             }
-        } catch(final RestconfDocumentedException e) {
-            throw e;
-        } catch (final Exception e) {
-            final String errMsg = "Error creating data ";
-            LOG.info(errMsg + (uriInfo != null ? uriInfo.getPath() : ""), e);
-            throw new RestconfDocumentedException(errMsg, e);
+
+            @Override
+            public void onFailure(final Throwable t) {
+                waiter.countDown();
+                handlerLoggerPost(t, uriInfo);
+            }
+        });
+
+        try {
+            waiter.await();
+        } catch (final InterruptedException e) {
+            final String msg = "Problem while waiting for response";
+            LOG.warn(msg);
+            throw new RestconfDocumentedException(msg, e);
         }
 
         final ResponseBuilder responseBuilder = Response.status(Status.NO_CONTENT);
@@ -824,6 +894,16 @@ public class RestconfImpl implements RestconfService {
         return responseBuilder.build();
     }
 
+    protected void handlerLoggerPost(final Throwable t, final UriInfo uriInfo) {
+        if (t != null) {
+            final String errMsg = "Error creating data ";
+            LOG.warn(errMsg + (uriInfo != null ? uriInfo.getPath() : ""), t);
+            throw new RestconfDocumentedException(errMsg, t);
+        } else {
+            LOG.trace("Successfuly create data.");
+        }
+    }
+
     private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint, final YangInstanceIdentifier normalizedII) {
         if(uriInfo == null) {
             // This is null if invoked internally
@@ -847,25 +927,51 @@ public class RestconfImpl implements RestconfService {
         final DOMMountPoint mountPoint = iiWithData.getMountPoint();
         final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
 
-        try {
-            if (mountPoint != null) {
-                this.broker.commitConfigurationDataDelete(mountPoint, normalizedII);
-            } else {
-                this.broker.commitConfigurationDataDelete(normalizedII).get();
+        final CheckedFuture<Void, TransactionCommitFailedException> future;
+        if (mountPoint != null) {
+            future = this.broker.commitConfigurationDataDelete(mountPoint, normalizedII);
+        } else {
+            future = this.broker.commitConfigurationDataDelete(normalizedII);
+        }
+
+        final CountDownLatch waiter = new CountDownLatch(1);
+        Futures.addCallback(future, new FutureCallback<Void>() {
+
+            @Override
+            public void onSuccess(final Void result) {
+                handlerLoggerDelete(null);
+                waiter.countDown();
             }
-        } catch (final Exception e) {
-            final Optional<Throwable> searchedException = Iterables.tryFind(Throwables.getCausalChain(e),
-                    Predicates.instanceOf(ModifiedNodeDoesNotExistException.class));
-            if (searchedException.isPresent()) {
-                throw new RestconfDocumentedException("Data specified for deleting doesn't exist.", ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
+
+            @Override
+            public void onFailure(final Throwable t) {
+                waiter.countDown();
+                handlerLoggerDelete(t);
             }
-            final String errMsg = "Error while deleting data";
-            LOG.info(errMsg, e);
-            throw new RestconfDocumentedException(errMsg, e);
+
+        });
+
+        try {
+            waiter.await();
+        } catch (final InterruptedException e) {
+            final String msg = "Problem while waiting for response";
+            LOG.warn(msg);
+            throw new RestconfDocumentedException(msg, e);
         }
+
         return Response.status(Status.OK).build();
     }
 
+    protected void handlerLoggerDelete(final Throwable t) {
+        if (t != null) {
+            final String errMsg = "Error while deleting data";
+            LOG.info(errMsg, t);
+            throw new RestconfDocumentedException(errMsg, t);
+        } else {
+            LOG.trace("Successfuly delete data.");
+        }
+    }
+
     /**
      * Subscribes to some path in schema context (stream) to listen on changes on this stream.
      *
@@ -877,6 +983,64 @@ public class RestconfImpl implements RestconfService {
      */
     @Override
     public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
+        if (identifier.contains(DATA_SUBSCR)) {
+            return dataSubs(identifier, uriInfo);
+        } else if (identifier.contains(NOTIFICATION_STREAM)) {
+            return notifStream(identifier, uriInfo);
+        }
+        final String msg = "Bad type of notification of sal-remote";
+        LOG.warn(msg);
+        throw new RestconfDocumentedException(msg);
+    }
+
+    /**
+     * Register notification listener by stream name
+     *
+     * @param identifier
+     *            - stream name
+     * @param uriInfo
+     *            - uriInfo
+     * @return {@link Response}
+     */
+    private Response notifStream(final String identifier, final UriInfo uriInfo) {
+        final String streamName = Notificator.createStreamNameFromUri(identifier);
+        if (Strings.isNullOrEmpty(streamName)) {
+            throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+        }
+        final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+        if ((listeners == null) || listeners.isEmpty()) {
+            throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
+                    ErrorTag.UNKNOWN_ELEMENT);
+        }
+
+        for (final NotificationListenerAdapter listener : listeners) {
+            this.broker.registerToListenNotification(listener);
+        }
+
+        final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
+        int notificationPort = NOTIFICATION_PORT;
+        try {
+            final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance();
+            notificationPort = webSocketServerInstance.getPort();
+        } catch (final NullPointerException e) {
+            WebSocketServer.createInstance(NOTIFICATION_PORT);
+        }
+        final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme("ws");
+        final URI uriToWebsocketServer = uriToWebsocketServerBuilder.replacePath(streamName).build();
+
+        return Response.status(Status.OK).location(uriToWebsocketServer).build();
+    }
+
+    /**
+     * Register data change listener by stream name
+     *
+     * @param identifier
+     *            - stream name
+     * @param uriInfo
+     *            - uri info
+     * @return {@link Response}
+     */
+    private Response dataSubs(final String identifier, final UriInfo uriInfo) {
         final String streamName = Notificator.createStreamNameFromUri(identifier);
         if (Strings.isNullOrEmpty(streamName)) {
             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
@@ -921,7 +1085,14 @@ public class RestconfImpl implements RestconfService {
         if (context == null) {
             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
         }
-        return this.broker.patchConfigurationDataWithinTransaction(context, this.controllerContext.getGlobalSchema());
+
+        try {
+            return this.broker.patchConfigurationDataWithinTransaction(context,
+                    this.controllerContext.getGlobalSchema());
+        } catch (final InterruptedException e) {
+            LOG.debug("Patch transaction failed", e);
+            throw new RestconfDocumentedException(e.getMessage());
+        }
     }
 
     @Override
@@ -929,13 +1100,20 @@ public class RestconfImpl implements RestconfService {
         if (context == null) {
             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
         }
-        return this.broker.patchConfigurationDataWithinTransaction(context, this.controllerContext.getGlobalSchema());
+
+        try {
+            return this.broker.patchConfigurationDataWithinTransaction(context,
+                    this.controllerContext.getGlobalSchema());
+        } catch (final InterruptedException e) {
+            LOG.debug("Patch transaction failed", e);
+            throw new RestconfDocumentedException(e.getMessage());
+        }
     }
 
     /**
      * Load parameter for subscribing to stream from input composite node
      *
-     * @param compNode
+     * @param value
      *            contains value
      * @return enum object if its string value is equal to {@code paramName}. In other cases null.
      */
@@ -1104,4 +1282,89 @@ public class RestconfImpl implements RestconfService {
 
         return streamNodeValues.build();
     }
+
+    /**
+     * Prepare stream for notification
+     *
+     * @param payload
+     *            - contains list of qnames of notifications
+     * @return - checked future object
+     */
+    private CheckedFuture<DOMRpcResult, DOMRpcException> invokeSalRemoteRpcNotifiStrRPC(
+            final NormalizedNodeContext payload) {
+        final ContainerNode data = (ContainerNode) payload.getData();
+        LeafSetNode leafSet = null;
+        String outputType = "XML";
+        for (final DataContainerChild<? extends PathArgument, ?> dataChild : data.getValue()) {
+            if (dataChild instanceof LeafSetNode) {
+                leafSet = (LeafSetNode) dataChild;
+            } else if (dataChild instanceof AugmentationNode) {
+                outputType = (String) (((AugmentationNode) dataChild).getValue()).iterator().next().getValue();
+            }
+        }
+
+        final Collection<LeafSetEntryNode> entryNodes = leafSet.getValue();
+        final List<SchemaPath> paths = new ArrayList<>();
+        String streamName = CREATE_NOTIFICATION_STREAM + "/";
+
+        final Iterator<LeafSetEntryNode> iterator = entryNodes.iterator();
+        while (iterator.hasNext()) {
+            final QName valueQName = QName.create((String) iterator.next().getValue());
+            final Module module = ControllerContext.getInstance()
+                    .findModuleByNamespace(valueQName.getModule().getNamespace());
+            Preconditions.checkNotNull(module, "Module for namespace " + valueQName.getModule().getNamespace()
+                    + " does not exist");
+            NotificationDefinition notifiDef = null;
+            for (final NotificationDefinition notification : module.getNotifications()) {
+                if (notification.getQName().equals(valueQName)) {
+                    notifiDef = notification;
+                    break;
+                }
+            }
+            final String moduleName = module.getName();
+            Preconditions.checkNotNull(notifiDef,
+                    "Notification " + valueQName + "doesn't exist in module " + moduleName);
+            paths.add(notifiDef.getPath());
+            streamName = streamName + moduleName + ":" + valueQName.getLocalName();
+            if (iterator.hasNext()) {
+                streamName = streamName + ",";
+            }
+        }
+
+        final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
+        final QName outputQname = QName.create(rpcQName, "output");
+        final QName streamNameQname = QName.create(rpcQName, "notification-stream-identifier");
+
+        final ContainerNode output = ImmutableContainerNodeBuilder.create()
+                .withNodeIdentifier(new NodeIdentifier(outputQname))
+                .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
+
+        if (!Notificator.existNotificationListenerFor(streamName)) {
+            Notificator.createNotificationListener(paths, streamName, outputType);
+        }
+
+        final DOMRpcResult defaultDOMRpcResult = new DefaultDOMRpcResult(output);
+
+        return Futures.immediateCheckedFuture(defaultDOMRpcResult);
+    }
+
+    private class TryOfPutData {
+        int tries = 2;
+        boolean done = false;
+
+        void countDown() {
+            this.tries--;
+        }
+
+        void done() {
+            this.done = true;
+        }
+
+        boolean isDone() {
+            return this.done;
+        }
+        int countGet() {
+            return this.tries;
+        }
+    }
 }
index ce188748fbdfe34e7a3ac4a997cd5aa0f6c43541..71006b829284b11a6bc0345ca0176cd456da3ad9 100644 (file)
@@ -20,6 +20,7 @@ import org.opendaylight.controller.config.yang.md.sal.rest.connector.RestConnect
 import org.opendaylight.controller.config.yang.md.sal.rest.connector.Rpcs;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.sal.core.api.Broker.ProviderSession;
 import org.opendaylight.controller.sal.core.api.Provider;
@@ -48,16 +49,16 @@ public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnec
         BrokerFacade.getInstance().setContext(session);
         BrokerFacade.getInstance().setDomDataBroker( domDataBroker);
         final SchemaService schemaService = session.getService(SchemaService.class);
-        listenerRegistration = schemaService.registerSchemaContextListener(ControllerContext.getInstance());
+        this.listenerRegistration = schemaService.registerSchemaContextListener(ControllerContext.getInstance());
         BrokerFacade.getInstance().setRpcService(session.getService(DOMRpcService.class));
-
+        BrokerFacade.getInstance().setDomNotificationService(session.getService(DOMNotificationService.class));
 
         ControllerContext.getInstance().setSchemas(schemaService.getGlobalContext());
         ControllerContext.getInstance().setMountService(session.getService(DOMMountPointService.class));
 
-        webSocketServerThread = new Thread(WebSocketServer.createInstance(port.getValue().intValue()));
-        webSocketServerThread.setName("Web socket server on port " + port);
-        webSocketServerThread.start();
+        this.webSocketServerThread = new Thread(WebSocketServer.createInstance(this.port.getValue().intValue()));
+        this.webSocketServerThread.setName("Web socket server on port " + this.port);
+        this.webSocketServerThread.start();
     }
 
     @Override
@@ -68,12 +69,12 @@ public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnec
     @Override
     public void close() {
 
-        if (listenerRegistration != null) {
-            listenerRegistration.close();
+        if (this.listenerRegistration != null) {
+            this.listenerRegistration.close();
         }
 
         WebSocketServer.destroyInstance();
-        webSocketServerThread.interrupt();
+        this.webSocketServerThread.interrupt();
     }
 
     @Override
@@ -81,27 +82,27 @@ public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnec
         final Config config = new Config();
 
         final Get get = new Get();
-        get.setReceivedRequests(stats.getConfigGet());
-        get.setSuccessfulResponses(stats.getSuccessGetConfig());
-        get.setFailedResponses(stats.getFailureGetConfig());
+        get.setReceivedRequests(this.stats.getConfigGet());
+        get.setSuccessfulResponses(this.stats.getSuccessGetConfig());
+        get.setFailedResponses(this.stats.getFailureGetConfig());
         config.setGet(get);
 
         final Post post = new Post();
-        post.setReceivedRequests(stats.getConfigPost());
-        post.setSuccessfulResponses(stats.getSuccessPost());
-        post.setFailedResponses(stats.getFailurePost());
+        post.setReceivedRequests(this.stats.getConfigPost());
+        post.setSuccessfulResponses(this.stats.getSuccessPost());
+        post.setFailedResponses(this.stats.getFailurePost());
         config.setPost(post);
 
         final Put put = new Put();
-        put.setReceivedRequests(stats.getConfigPut());
-        put.setSuccessfulResponses(stats.getSuccessPut());
-        put.setFailedResponses(stats.getFailurePut());
+        put.setReceivedRequests(this.stats.getConfigPut());
+        put.setSuccessfulResponses(this.stats.getSuccessPut());
+        put.setFailedResponses(this.stats.getFailurePut());
         config.setPut(put);
 
         final Delete delete = new Delete();
-        delete.setReceivedRequests(stats.getConfigDelete());
-        delete.setSuccessfulResponses(stats.getSuccessDelete());
-        delete.setFailedResponses(stats.getFailureDelete());
+        delete.setReceivedRequests(this.stats.getConfigDelete());
+        delete.setSuccessfulResponses(this.stats.getSuccessDelete());
+        delete.setFailedResponses(this.stats.getFailureDelete());
         config.setDelete(delete);
 
         return config;
@@ -109,19 +110,19 @@ public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnec
 
     @Override
     public Operational getOperational() {
-        final BigInteger opGet = stats.getOperationalGet();
+        final BigInteger opGet = this.stats.getOperationalGet();
         final Operational operational = new Operational();
         final Get get = new Get();
         get.setReceivedRequests(opGet);
-        get.setSuccessfulResponses(stats.getSuccessGetOperational());
-        get.setFailedResponses(stats.getFailureGetOperational());
+        get.setSuccessfulResponses(this.stats.getSuccessGetOperational());
+        get.setFailedResponses(this.stats.getFailureGetOperational());
         operational.setGet(get);
         return operational;
     }
 
     @Override
     public Rpcs getRpcs() {
-        final BigInteger rpcInvoke = stats.getRpc();
+        final BigInteger rpcInvoke = this.stats.getRpc();
         final Rpcs rpcs = new Rpcs();
         rpcs.setReceivedRequests(rpcInvoke);
         return rpcs;
index 51286725770a045244088329fa893b61c55b7f60..a8651c21638a93477a4f4ad933e141306ecdaf2f 100644 (file)
@@ -77,7 +77,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
     private static final TransformerFactory FACTORY = TransformerFactory.newInstance();
     private static final Pattern RFC3339_PATTERN = Pattern.compile("(\\d\\d)(\\d\\d)$");
 
-    private final SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ");
+    private static final SimpleDateFormat RFC3339 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ");
 
     private final YangInstanceIdentifier path;
     private ListenerRegistration<DOMDataChangeListener> registration;
@@ -96,12 +96,12 @@ public class ListenerAdapter implements DOMDataChangeListener {
      */
     ListenerAdapter(final YangInstanceIdentifier path, final String streamName) {
         Preconditions.checkNotNull(path);
-        Preconditions.checkArgument(streamName != null && !streamName.isEmpty());
+        Preconditions.checkArgument((streamName != null) && !streamName.isEmpty());
         this.path = path;
         this.streamName = streamName;
-        eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor());
-        eventBusChangeRecorder = new EventBusChangeRecorder();
-        eventBus.register(eventBusChangeRecorder);
+        this.eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor());
+        this.eventBusChangeRecorder = new EventBusChangeRecorder();
+        this.eventBus.register(this.eventBusChangeRecorder);
     }
 
     @Override
@@ -111,7 +111,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
             final String xml = prepareXmlFrom(change);
             final Event event = new Event(EventType.NOTIFY);
             event.setData(xml);
-            eventBus.post(event);
+            this.eventBus.post(event);
         }
     }
 
@@ -123,20 +123,20 @@ public class ListenerAdapter implements DOMDataChangeListener {
         public void recordCustomerChange(final Event event) {
             if (event.getType() == EventType.REGISTER) {
                 final Channel subscriber = event.getSubscriber();
-                if (!subscribers.contains(subscriber)) {
-                    subscribers.add(subscriber);
+                if (!ListenerAdapter.this.subscribers.contains(subscriber)) {
+                    ListenerAdapter.this.subscribers.add(subscriber);
                 }
             } else if (event.getType() == EventType.DEREGISTER) {
-                subscribers.remove(event.getSubscriber());
+                ListenerAdapter.this.subscribers.remove(event.getSubscriber());
                 Notificator.removeListenerIfNoSubscriberExists(ListenerAdapter.this);
             } else if (event.getType() == EventType.NOTIFY) {
-                for (final Channel subscriber : subscribers) {
+                for (final Channel subscriber : ListenerAdapter.this.subscribers) {
                     if (subscriber.isActive()) {
                         LOG.debug("Data are sent to subscriber {}:", subscriber.remoteAddress());
                         subscriber.writeAndFlush(new TextWebSocketFrame(event.getData()));
                     } else {
                         LOG.debug("Subscriber {} is removed - channel is not active yet.", subscriber.remoteAddress());
-                        subscribers.remove(subscriber);
+                        ListenerAdapter.this.subscribers.remove(subscriber);
                     }
                 }
             }
@@ -167,7 +167,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
          * @return Channel
          */
         public Channel getSubscriber() {
-            return subscriber;
+            return this.subscriber;
         }
 
         /**
@@ -186,7 +186,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
          * @return String representation of event data.
          */
         public String getData() {
-            return data;
+            return this.data;
         }
 
         /**
@@ -204,7 +204,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
          * @return The type of the event.
          */
         public EventType getType() {
-            return type;
+            return this.type;
         }
     }
 
@@ -267,15 +267,15 @@ public class ListenerAdapter implements DOMDataChangeListener {
      *            Date
      * @return Data specified by RFC3339.
      */
-    private String toRFC3339(final Date d) {
-        return RFC3339_PATTERN.matcher(rfc3339.format(d)).replaceAll("$1:$2");
+    public static String toRFC3339(final Date d) {
+        return RFC3339_PATTERN.matcher(RFC3339.format(d)).replaceAll("$1:$2");
     }
 
     /**
      * Creates {@link Document} document.
      * @return {@link Document} document.
      */
-    private Document createDocument() {
+    public static Document createDocument() {
         final DocumentBuilder bob;
         try {
             bob = DBF.newDocumentBuilder();
@@ -326,7 +326,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
      */
     private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data, final Element element,
             final Operation operation) {
-        if (data == null || data.isEmpty()) {
+        if ((data == null) || data.isEmpty()) {
             return;
         }
         for (final YangInstanceIdentifier path : data) {
@@ -340,10 +340,10 @@ public class ListenerAdapter implements DOMDataChangeListener {
     private void addCreatedChangedValuesFromDataToElement(final Document doc, final Set<Entry<YangInstanceIdentifier,
                 NormalizedNode<?,?>>> data, final Element element, final Operation operation, final SchemaContext
             schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
-        if (data == null || data.isEmpty()) {
+        if ((data == null) || data.isEmpty()) {
             return;
         }
-        for (Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry : data) {
+        for (final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry : data) {
             if (!ControllerContext.getInstance().isNodeMixin(entry.getKey())) {
                 final Node node = createCreatedChangedDataChangeEventElement(doc, entry, operation, schemaContext,
                         dataSchemaContextTree);
@@ -396,18 +396,19 @@ public class ListenerAdapter implements DOMDataChangeListener {
             final Element dataElement = doc.createElement("data");
             dataElement.appendChild(result);
             dataChangeEventElement.appendChild(dataElement);
-        } catch (IOException e) {
+        } catch (final IOException e) {
             LOG.error("Error in writer ", e);
-        } catch (XMLStreamException e) {
+        } catch (final XMLStreamException e) {
             LOG.error("Error processing stream", e);
         }
 
         return dataChangeEventElement;
     }
 
-    private static DOMResult writeNormalizedNode(final NormalizedNode<?,?> normalized, final
-        YangInstanceIdentifier path, final SchemaContext context, final DataSchemaContextTree dataSchemaContextTree) throws
-            IOException, XMLStreamException {
+    private static DOMResult writeNormalizedNode(final NormalizedNode<?, ?> normalized,
+                                                 final YangInstanceIdentifier path, final SchemaContext context,
+                                                 final DataSchemaContextTree dataSchemaContextTree)
+            throws IOException, XMLStreamException {
         final XMLOutputFactory XML_FACTORY = XMLOutputFactory.newFactory();
         final Document doc = XmlDocumentUtils.getDocument();
         final DOMResult result = new DOMResult(doc);
@@ -416,7 +417,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
         XMLStreamWriter writer = null;
         final SchemaPath nodePath;
 
-        if (normalized instanceof MapEntryNode || normalized instanceof UnkeyedListEntryNode) {
+        if ((normalized instanceof MapEntryNode) || (normalized instanceof UnkeyedListEntryNode)) {
             nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath();
         } else {
             nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath().getParent();
@@ -542,7 +543,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
      * @return Path pointed to data in data store.
      */
     public YangInstanceIdentifier getPath() {
-        return path;
+        return this.path;
     }
 
     /**
@@ -560,17 +561,17 @@ public class ListenerAdapter implements DOMDataChangeListener {
      * @return The name of the stream.
      */
     public String getStreamName() {
-        return streamName;
+        return this.streamName;
     }
 
     /**
      * Removes all subscribers and unregisters event bus change recorder form event bus.
      */
     public void close() throws Exception {
-        subscribers = new ConcurrentSet<>();
-        registration.close();
-        registration = null;
-        eventBus.unregister(eventBusChangeRecorder);
+        this.subscribers = new ConcurrentSet<>();
+        this.registration.close();
+        this.registration = null;
+        this.eventBus.unregister(this.eventBusChangeRecorder);
     }
 
     /**
@@ -579,7 +580,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
      * @return True if exist, false otherwise.
      */
     public boolean isListening() {
-        return registration == null ? false : true;
+        return this.registration == null ? false : true;
     }
 
     /**
@@ -595,7 +596,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
         }
         final Event event = new Event(EventType.REGISTER);
         event.setSubscriber(subscriber);
-        eventBus.post(event);
+        this.eventBus.post(event);
     }
 
     /**
@@ -608,7 +609,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
         LOG.debug("Subscriber {} is removed.", subscriber.remoteAddress());
         final Event event = new Event(EventType.DEREGISTER);
         event.setSubscriber(subscriber);
-        eventBus.post(event);
+        this.eventBus.post(event);
     }
 
     /**
@@ -617,7 +618,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
      * @return True if exist at least one {@link Channel} subscriber, false otherwise.
      */
     public boolean hasSubscribers() {
-        return !subscribers.isEmpty();
+        return !this.subscribers.isEmpty();
     }
 
     /**
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java
new file mode 100644 (file)
index 0000000..42f0996
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * 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.netconf.sal.streams.listeners;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.util.internal.ConcurrentSet;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.json.JSONObject;
+import org.json.XML;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * {@link NotificationListenerAdapter} is responsible to track events on
+ * notifications.
+ *
+ */
+public class NotificationListenerAdapter implements DOMNotificationListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NotificationListenerAdapter.class);
+    private static final TransformerFactory FACTORY = TransformerFactory.newInstance();
+
+    private final String streamName;
+    private ListenerRegistration<DOMNotificationListener> registration;
+    private Set<Channel> subscribers = new ConcurrentSet<>();
+    private final EventBus eventBus;
+    private final EventBusChangeRecorder eventBusChangeRecorder;
+
+    private final SchemaPath path;
+    private final String outputType;
+
+    /**
+     * Set path of listener and stream name, register event bus.
+     *
+     * @param path
+     *            - path of notification
+     * @param streamName
+     *            - stream name of listener
+     * @param outputType
+     *            - type of output on notification (JSON, XML)
+     */
+    NotificationListenerAdapter(final SchemaPath path, final String streamName, final String outputType) {
+        this.outputType = outputType;
+        Preconditions.checkArgument((streamName != null) && !streamName.isEmpty());
+        Preconditions.checkArgument(path != null);
+        this.path = path;
+        this.streamName = streamName;
+        this.eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor());
+        this.eventBusChangeRecorder = new EventBusChangeRecorder();
+        this.eventBus.register(this.eventBusChangeRecorder);
+    }
+
+    @Override
+    public void onNotification(final DOMNotification notification) {
+        final String xml = prepareXmlFrom(notification);
+        final Event event = new Event(EventType.NOTIFY);
+        if (this.outputType.equals("JSON")) {
+            final JSONObject jsonObject = XML.toJSONObject(xml);
+            event.setData(jsonObject.toString());
+        } else {
+            event.setData(xml);
+        }
+        this.eventBus.post(event);
+    }
+
+    /**
+     * Checks if exists at least one {@link Channel} subscriber.
+     *
+     * @return True if exist at least one {@link Channel} subscriber, false
+     *         otherwise.
+     */
+    public boolean hasSubscribers() {
+        return !this.subscribers.isEmpty();
+    }
+
+    /**
+     * Reset lists, close registration and unregister bus event.
+     */
+    public void close() {
+        this.subscribers = new ConcurrentSet<>();
+        this.registration.close();
+        this.registration = null;
+        this.eventBus.unregister(this.eventBusChangeRecorder);
+    }
+
+    /**
+     * Get stream name of this listener
+     *
+     * @return {@link String}
+     */
+    public String getStreamName() {
+        return this.streamName;
+    }
+
+    /**
+     * Check if is this listener registered.
+     *
+     * @return - true if is registered, otherwise null
+     */
+    public boolean isListening() {
+        return this.registration == null ? false : true;
+    }
+
+    /**
+     * Get schema path of notification
+     *
+     * @return {@link SchemaPath}
+     */
+    public SchemaPath getSchemaPath() {
+        return this.path;
+    }
+
+    /**
+     * Set registration for close after closing connection and check if this
+     * listener is registered
+     *
+     * @param registration
+     *            - registered listener
+     */
+    public void setRegistration(final ListenerRegistration<DOMNotificationListener> registration) {
+        Preconditions.checkNotNull(registration);
+        this.registration = registration;
+    }
+
+    /**
+     * Creates event of type {@link EventType#REGISTER}, set {@link Channel}
+     * subscriber to the event and post event into event bus.
+     *
+     * @param subscriber
+     *            Channel
+     */
+    public void addSubscriber(final Channel subscriber) {
+        if (!subscriber.isActive()) {
+            LOG.debug("Channel is not active between websocket server and subscriber {}" + subscriber.remoteAddress());
+        }
+        final Event event = new Event(EventType.REGISTER);
+        event.setSubscriber(subscriber);
+        this.eventBus.post(event);
+    }
+
+    /**
+     * Creates event of type {@link EventType#DEREGISTER}, sets {@link Channel}
+     * subscriber to the event and posts event into event bus.
+     *
+     * @param subscriber
+     */
+    public void removeSubscriber(final Channel subscriber) {
+        LOG.debug("Subscriber {} is removed.", subscriber.remoteAddress());
+        final Event event = new Event(EventType.DEREGISTER);
+        event.setSubscriber(subscriber);
+        this.eventBus.post(event);
+    }
+
+    private String prepareXmlFrom(final DOMNotification notification) {
+        final SchemaContext schemaContext = ControllerContext.getInstance().getGlobalSchema();
+        final Document doc = ListenerAdapter.createDocument();
+        final Element notificationElement = doc.createElementNS("urn:ietf:params:xml:ns:netconf:notification:1.0",
+                "notification");
+        doc.appendChild(notificationElement);
+
+        final Element eventTimeElement = doc.createElement("eventTime");
+        eventTimeElement.setTextContent(ListenerAdapter.toRFC3339(new Date()));
+        notificationElement.appendChild(eventTimeElement);
+
+        final Element notificationEventElement = doc.createElementNS(
+                "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "create-notification-stream");
+        addValuesToNotificationEventElement(doc, notificationEventElement, notification, schemaContext);
+        notificationElement.appendChild(notificationEventElement);
+
+        try {
+            final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            final Transformer transformer = FACTORY.newTransformer();
+            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+            transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out, Charsets.UTF_8)));
+            final byte[] charData = out.toByteArray();
+            return new String(charData, "UTF-8");
+        } catch (TransformerException | UnsupportedEncodingException e) {
+            final String msg = "Error during transformation of Document into String";
+            LOG.error(msg, e);
+            return msg;
+        }
+    }
+
+    private void addValuesToNotificationEventElement(final Document doc, final Element element,
+            final DOMNotification notification, final SchemaContext schemaContext) {
+        if (notification == null) {
+            return;
+        }
+
+        final NormalizedNode<NodeIdentifier, Collection<DataContainerChild<? extends PathArgument, ?>>> body = notification
+                .getBody();
+        try {
+            final DOMResult domResult = writeNormalizedNode(body,
+                    YangInstanceIdentifier.create(body.getIdentifier()), schemaContext);
+            final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
+            final Element dataElement = doc.createElement("notification");
+            dataElement.appendChild(result);
+            element.appendChild(dataElement);
+        } catch (final IOException e) {
+            LOG.error("Error in writer ", e);
+        } catch (final XMLStreamException e) {
+            LOG.error("Error processing stream", e);
+        }
+    }
+
+    private DOMResult writeNormalizedNode(final NormalizedNode<?, ?> normalized, final YangInstanceIdentifier path,
+            final SchemaContext context) throws IOException, XMLStreamException {
+        final XMLOutputFactory XML_FACTORY = XMLOutputFactory.newFactory();
+        final Document doc = XmlDocumentUtils.getDocument();
+        final DOMResult result = new DOMResult(doc);
+        NormalizedNodeWriter normalizedNodeWriter = null;
+        NormalizedNodeStreamWriter normalizedNodeStreamWriter = null;
+        XMLStreamWriter writer = null;
+
+        try {
+            writer = XML_FACTORY.createXMLStreamWriter(result);
+            normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, context,
+                    this.getSchemaPath());
+            normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter);
+
+            normalizedNodeWriter.write(normalized);
+
+            normalizedNodeWriter.flush();
+        } finally {
+            if (normalizedNodeWriter != null) {
+                normalizedNodeWriter.close();
+            }
+            if (normalizedNodeStreamWriter != null) {
+                normalizedNodeStreamWriter.close();
+            }
+            if (writer != null) {
+                writer.close();
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Tracks events of data change by customer.
+     */
+    private final class EventBusChangeRecorder {
+        @Subscribe
+        public void recordCustomerChange(final Event event) {
+            if (event.getType() == EventType.REGISTER) {
+                final Channel subscriber = event.getSubscriber();
+                if (!NotificationListenerAdapter.this.subscribers.contains(subscriber)) {
+                    NotificationListenerAdapter.this.subscribers.add(subscriber);
+                }
+            } else if (event.getType() == EventType.DEREGISTER) {
+                NotificationListenerAdapter.this.subscribers.remove(event.getSubscriber());
+                Notificator.removeNotificationListenerIfNoSubscriberExists(NotificationListenerAdapter.this);
+            } else if (event.getType() == EventType.NOTIFY) {
+                for (final Channel subscriber : NotificationListenerAdapter.this.subscribers) {
+                    if (subscriber.isActive()) {
+                        LOG.debug("Data are sent to subscriber {}:", subscriber.remoteAddress());
+                        subscriber.writeAndFlush(new TextWebSocketFrame(event.getData()));
+                    } else {
+                        LOG.debug("Subscriber {} is removed - channel is not active yet.", subscriber.remoteAddress());
+                        NotificationListenerAdapter.this.subscribers.remove(subscriber);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Represents event of specific {@link EventType} type, holds data and
+     * {@link Channel} subscriber.
+     */
+    private final class Event {
+        private final EventType type;
+        private Channel subscriber;
+        private String data;
+
+        /**
+         * Creates new event specified by {@link EventType} type.
+         *
+         * @param type
+         *            EventType
+         */
+        public Event(final EventType type) {
+            this.type = type;
+        }
+
+        /**
+         * Gets the {@link Channel} subscriber.
+         *
+         * @return Channel
+         */
+        public Channel getSubscriber() {
+            return this.subscriber;
+        }
+
+        /**
+         * Sets subscriber for event.
+         *
+         * @param subscriber
+         *            Channel
+         */
+        public void setSubscriber(final Channel subscriber) {
+            this.subscriber = subscriber;
+        }
+
+        /**
+         * Gets event String.
+         *
+         * @return String representation of event data.
+         */
+        public String getData() {
+            return this.data;
+        }
+
+        /**
+         * Sets event data.
+         *
+         * @param data
+         *            String.
+         */
+        public void setData(final String data) {
+            this.data = data;
+        }
+
+        /**
+         * Gets event type.
+         *
+         * @return The type of the event.
+         */
+        public EventType getType() {
+            return this.type;
+        }
+    }
+
+    /**
+     * Type of the event.
+     */
+    private enum EventType {
+        REGISTER, DEREGISTER, NOTIFY;
+    }
+}
index 9537732133e011180f0bf054cead560d06f9b6dc..0bd38652b766634f4b28088a4210d0c005beb2e7 100644 (file)
@@ -7,12 +7,17 @@
  */
 package org.opendaylight.netconf.sal.streams.listeners;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * {@link Notificator} is responsible to create, remove and find
@@ -21,6 +26,9 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 public class Notificator {
 
     private static Map<String, ListenerAdapter> listenersByStreamName = new ConcurrentHashMap<>();
+    private static Map<String, List<NotificationListenerAdapter>> notificationListenersByStreamName = new ConcurrentHashMap<>();
+
+    private static final Logger LOG = LoggerFactory.getLogger(Notificator.class);
     private static final Lock lock = new ReentrantLock();
 
     private Notificator() {
@@ -40,7 +48,7 @@ public class Notificator {
      *            The name of the stream.
      * @return {@link ListenerAdapter} specified by stream name.
      */
-    public static ListenerAdapter getListenerFor(String streamName) {
+    public static ListenerAdapter getListenerFor(final String streamName) {
         return listenersByStreamName.get(streamName);
     }
 
@@ -50,7 +58,7 @@ public class Notificator {
      * @param streamName
      * @return True if the listener exist, false otherwise.
      */
-    public static boolean existListenerFor(String streamName) {
+    public static boolean existListenerFor(final String streamName) {
         return listenersByStreamName.containsKey(streamName);
     }
 
@@ -63,8 +71,8 @@ public class Notificator {
      *            The name of the stream.
      * @return New {@link ListenerAdapter} listener from {@link YangInstanceIdentifier} path and stream name.
      */
-    public static ListenerAdapter createListener(YangInstanceIdentifier path, String streamName) {
-        ListenerAdapter listener = new ListenerAdapter(path, streamName);
+    public static ListenerAdapter createListener(final YangInstanceIdentifier path, final String streamName) {
+        final ListenerAdapter listener = new ListenerAdapter(path, streamName);
         try {
             lock.lock();
             listenersByStreamName.put(streamName, listener);
@@ -82,7 +90,7 @@ public class Notificator {
      *            URI for creation stream name.
      * @return String representation of stream name.
      */
-    public static String createStreamNameFromUri(String uri) {
+    public static String createStreamNameFromUri(final String uri) {
         if (uri == null) {
             return null;
         }
@@ -100,10 +108,11 @@ public class Notificator {
      * Removes all listeners.
      */
     public static void removeAllListeners() {
-        for (ListenerAdapter listener : listenersByStreamName.values()) {
+        for (final ListenerAdapter listener : listenersByStreamName.values()) {
             try {
                 listener.close();
-            } catch (Exception e) {
+            } catch (final Exception e) {
+                LOG.error("Failed to close listener", e);
             }
         }
         try {
@@ -120,7 +129,7 @@ public class Notificator {
      * @param listener
      *            ListenerAdapter
      */
-    public static void removeListenerIfNoSubscriberExists(ListenerAdapter listener) {
+    public static void removeListenerIfNoSubscriberExists(final ListenerAdapter listener) {
         if (!listener.hasSubscribers()) {
             deleteListener(listener);
         }
@@ -132,11 +141,12 @@ public class Notificator {
      * @param listener
      *            ListenerAdapter
      */
-    private static void deleteListener(ListenerAdapter listener) {
+    private static void deleteListener(final ListenerAdapter listener) {
         if (listener != null) {
             try {
                 listener.close();
-            } catch (Exception e) {
+            } catch (final Exception e) {
+                LOG.error("Failed to close listener", e);
             }
             try {
                 lock.lock();
@@ -147,4 +157,57 @@ public class Notificator {
         }
     }
 
+    /**
+     * Check if the listener specified by qnames of request exist.
+     *
+     * @param streamName
+     *            - name of stream
+     * @return True if the listener exist, false otherwise.
+     */
+    public static boolean existNotificationListenerFor(final String streamName) {
+        return notificationListenersByStreamName.containsKey(streamName);
+
+    }
+
+    public static List<NotificationListenerAdapter> createNotificationListener(final List<SchemaPath> paths,
+            final String streamName, final String outputType) {
+        final List<NotificationListenerAdapter> listListeners = new ArrayList<>();
+        for (final SchemaPath path : paths) {
+            final NotificationListenerAdapter listener = new NotificationListenerAdapter(path, streamName, outputType);
+            listListeners.add(listener);
+        }
+        try {
+            lock.lock();
+            notificationListenersByStreamName.put(streamName, listListeners);
+        } finally {
+            lock.unlock();
+        }
+        return listListeners;
+    }
+
+    public static void removeNotificationListenerIfNoSubscriberExists(final NotificationListenerAdapter listener) {
+        if (!listener.hasSubscribers()) {
+            deleteNotificationListener(listener);
+        }
+    }
+
+    private static void deleteNotificationListener(final NotificationListenerAdapter listener) {
+        if (listener != null) {
+            try {
+                listener.close();
+            } catch (final Exception e) {
+                LOG.error("Failed to close listener", e);
+            }
+            try {
+                lock.lock();
+                notificationListenersByStreamName.remove(listener.getStreamName());
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    public static List<NotificationListenerAdapter> getNotificationListenerFor(final String streamName) {
+        return notificationListenersByStreamName.get(streamName);
+    }
 }
index c2b4edc6214d8f9042eceb7c8e99d936e877c2a3..5af22bfafc7d27bcd565f62ad722242e04c513a9 100644 (file)
@@ -8,9 +8,9 @@
 
 package org.opendaylight.netconf.sal.streams.websockets;
 
+import static io.netty.handler.codec.http.HttpHeaders.Names.HOST;
 import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
 import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
-import static io.netty.handler.codec.http.HttpHeaders.Names.HOST;
 import static io.netty.handler.codec.http.HttpMethod.GET;
 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
@@ -35,7 +35,10 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
 import io.netty.util.CharsetUtil;
 import java.io.IOException;
+import java.util.List;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -80,24 +83,37 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
             return;
         }
 
-        String streamName = Notificator.createStreamNameFromUri(req.getUri());
-        ListenerAdapter listener = Notificator.getListenerFor(streamName);
-        if (listener != null) {
-            listener.addSubscriber(ctx.channel());
-            logger.debug("Subscriber successfully registered.");
-        } else {
-            logger.error("Listener for stream with name '{}' was not found.", streamName);
-            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR));
+        final String streamName = Notificator.createStreamNameFromUri(req.getUri());
+        if (streamName.contains(RestconfImpl.DATA_SUBSCR)) {
+            final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+            if (listener != null) {
+                listener.addSubscriber(ctx.channel());
+                logger.debug("Subscriber successfully registered.");
+            } else {
+                logger.error("Listener for stream with name '{}' was not found.", streamName);
+                sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR));
+            }
+        } else if (streamName.contains(RestconfImpl.NOTIFICATION_STREAM)) {
+            final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+            if (!listeners.isEmpty() && (listeners != null)) {
+                for (final NotificationListenerAdapter listener : listeners) {
+                    listener.addSubscriber(ctx.channel());
+                    logger.debug("Subscriber successfully registered.");
+                }
+            } else {
+                logger.error("Listener for stream with name '{}' was not found.", streamName);
+                sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR));
+            }
         }
 
         // Handshake
-        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req),
+        final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req),
                 null, false);
-        handshaker = wsFactory.newHandshaker(req);
-        if (handshaker == null) {
+        this.handshaker = wsFactory.newHandshaker(req);
+        if (this.handshaker == null) {
             WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
         } else {
-            handshaker.handshake(ctx.channel(), req);
+            this.handshaker.handshake(ctx.channel(), req);
         }
 
     }
@@ -116,15 +132,15 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
             final FullHttpResponse res) {
         // Generate an error page if response getStatus code is not OK (200).
         if (res.getStatus().code() != 200) {
-            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
+            final ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
             res.content().writeBytes(buf);
             buf.release();
             setContentLength(res, res.content().readableBytes());
         }
 
         // Send the response and close the connection if necessary.
-        ChannelFuture f = ctx.channel().writeAndFlush(res);
-        if (!isKeepAlive(req) || res.getStatus().code() != 200) {
+        final ChannelFuture f = ctx.channel().writeAndFlush(res);
+        if (!isKeepAlive(req) || (res.getStatus().code() != 200)) {
             f.addListener(ChannelFutureListener.CLOSE);
         }
     }
@@ -139,14 +155,23 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
      */
     private void handleWebSocketFrame(final ChannelHandlerContext ctx, final WebSocketFrame frame) throws IOException {
         if (frame instanceof CloseWebSocketFrame) {
-            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
-            String streamName = Notificator.createStreamNameFromUri(((CloseWebSocketFrame) frame).reasonText());
-            ListenerAdapter listener = Notificator.getListenerFor(streamName);
-            if (listener != null) {
-                listener.removeSubscriber(ctx.channel());
-                logger.debug("Subscriber successfully registered.");
+            this.handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
+            final String streamName = Notificator.createStreamNameFromUri(((CloseWebSocketFrame) frame).reasonText());
+            if (streamName.contains(RestconfImpl.DATA_SUBSCR)) {
+                final ListenerAdapter listener = Notificator.getListenerFor(streamName);
+                if (listener != null) {
+                    listener.removeSubscriber(ctx.channel());
+                    logger.debug("Subscriber successfully registered.");
+                }
+                Notificator.removeListenerIfNoSubscriberExists(listener);
+            } else if (streamName.contains(RestconfImpl.NOTIFICATION_STREAM)) {
+                final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
+                if (!listeners.isEmpty() && (listeners != null)) {
+                    for (final NotificationListenerAdapter listener : listeners) {
+                        listener.removeSubscriber(ctx.channel());
+                    }
+                }
             }
-            Notificator.removeListenerIfNoSubscriberExists(listener);
             return;
         } else if (frame instanceof PingWebSocketFrame) {
             ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
@@ -156,7 +181,7 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
 
     @Override
     public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
-        if (cause instanceof java.nio.channels.ClosedChannelException == false) {
+        if ((cause instanceof java.nio.channels.ClosedChannelException) == false) {
             // cause.printStackTrace();
         }
         ctx.close();
index 8b5d8fd71cb4cb7b3425165ae1289d8741494f40..4a2886915707719a49ca74f23d7e08013a2bb5e0 100644 (file)
@@ -16,8 +16,13 @@ import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContentYinBodyWri
 import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeXmlBodyWriter;
+import org.opendaylight.netconf.sal.rest.impl.PATCHJsonBodyWriter;
+import org.opendaylight.netconf.sal.rest.impl.PATCHXmlBodyWriter;
+import org.opendaylight.netconf.sal.rest.impl.RestconfDocumentedExceptionMapper;
 import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader;
 import org.opendaylight.restconf.common.wrapper.services.Draft11ServicesWrapperImpl;
+import org.opendaylight.restconf.utils.patch.Draft11JsonToPATCHBodyReader;
+import org.opendaylight.restconf.utils.patch.Draft11XmlToPATCHBodyReader;
 
 public class RestconfApplication extends Application {
 
@@ -26,7 +31,9 @@ public class RestconfApplication extends Application {
         return ImmutableSet.<Class<?>> builder().add(NormalizedNodeJsonBodyWriter.class)
                 .add(NormalizedNodeXmlBodyWriter.class).add(JsonNormalizedNodeBodyReader.class)
                 .add(XmlNormalizedNodeBodyReader.class).add(SchemaExportContentYinBodyWriter.class)
-                .add(SchemaExportContentYangBodyWriter.class)
+                .add(Draft11JsonToPATCHBodyReader.class).add(Draft11XmlToPATCHBodyReader.class)
+                .add(PATCHJsonBodyWriter.class).add(PATCHXmlBodyWriter.class)
+                .add(SchemaExportContentYangBodyWriter.class).add(RestconfDocumentedExceptionMapper.class)
                 .build();
     }
 
index 6fc16fa232a78a18b0b2e4d0a2c87addb79e3fe5..00c3a2cd822e587233882dd83b690c5c10a8f2ee 100644 (file)
@@ -57,30 +57,26 @@ public final class YangInstanceIdentifierDeserializer {
                 data, DataSchemaContextTree.from(schemaContext).getRoot(),
                 YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
 
-        checkValid(!data.isEmpty(), "Empty path is not valid", variables.getData(), variables.getOffset());
-
-        if (!data.equals(String.valueOf(RestconfConstants.SLASH))) {
-            while (!allCharsConsumed(variables)) {
-                validArg(variables);
-                final QName qname = prepareQName(variables);
-
-                // this is the last identifier (input is consumed) or end of identifier (slash)
-                if (allCharsConsumed(variables)
-                        || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
-                    prepareIdentifier(qname, path, variables);
-                    path.add(variables.getCurrent().getIdentifier());
-                } else if (currentChar(variables.getOffset(),
-                        variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
-                    if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
-                        prepareNodeWithPredicates(qname, path, variables);
-                    } else {
-                        prepareNodeWithValue(qname, path, variables);
-                    }
+        while (!allCharsConsumed(variables)) {
+            validArg(variables);
+            final QName qname = prepareQName(variables);
+
+            // this is the last identifier (input is consumed) or end of identifier (slash)
+            if (allCharsConsumed(variables)
+                    || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
+                prepareIdentifier(qname, path, variables);
+                path.add(variables.getCurrent().getIdentifier());
+            } else if (currentChar(variables.getOffset(),
+                    variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
+                if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
+                    prepareNodeWithPredicates(qname, path, variables);
                 } else {
-                    throw new IllegalArgumentException(
-                            "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position "
-                                    + variables.getOffset() + ".");
+                    prepareNodeWithValue(qname, path, variables);
                 }
+            } else {
+                throw new IllegalArgumentException(
+                        "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position "
+                                + variables.getOffset() + ".");
             }
         }
 
@@ -287,11 +283,18 @@ public final class YangInstanceIdentifierDeserializer {
     }
 
     private static void validArg(final MainVarsWrapper variables) {
-        checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()),
-                "Identifier must start with '/'.", variables.getData(), variables.getOffset());
-        skipCurrentChar(variables);
-        checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.",
-                variables.getData(), variables.getOffset());
+        // every identifier except of the first MUST start with slash
+        if (variables.getOffset() != MainVarsWrapper.STARTING_OFFSET) {
+            checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()),
+                    "Identifier must start with '/'.", variables.getData(), variables.getOffset());
+
+            // skip slash
+            skipCurrentChar(variables);
+
+            // check if slash is not also the last char in identifier
+            checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.",
+                    variables.getData(), variables.getOffset());
+        }
     }
 
     private static void skipCurrentChar(final MainVarsWrapper variables) {
index 1384eac69d1d1f906a1cc029bf77866e83b75f1c..538dbdc3d40b84105b90d7f2a4bf5cef451d3f9a 100644 (file)
@@ -49,10 +49,7 @@ public final class YangInstanceIdentifierSerializer {
     public static String create(final SchemaContext schemaContext, final YangInstanceIdentifier data) {
         final DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
         final MainVarsWrapper variables = new MainVarsWrapper(current);
-
-        // for empty data return slash
-        final StringBuilder path = (data.getPathArguments().size() == 0) ?
-                new StringBuilder(String.valueOf(RestconfConstants.SLASH)) : new StringBuilder();
+        final StringBuilder path = new StringBuilder();
 
         QNameModule parentModule = null;
         for (int i = 0; i < data.getPathArguments().size(); i++) {
@@ -74,9 +71,13 @@ public final class YangInstanceIdentifierSerializer {
             // append namespace before every node which is defined in other module than its parent
             // condition is satisfied also for the first path argument
             if (!arg.getNodeType().getModule().equals(parentModule)) {
-                path.append(RestconfConstants.SLASH
-                        + prefixForNamespace(arg.getNodeType(), schemaContext)
-                        + ParserBuilderConstants.Deserializer.COLON);
+                // append slash if it is not the first path argument
+                if (path.length() > 0) {
+                    path.append(RestconfConstants.SLASH);
+                }
+
+                path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
+                path.append(ParserBuilderConstants.Deserializer.COLON);
             } else {
                 path.append(RestconfConstants.SLASH);
             }
index 62021d8aa345dbc7724602bcf27ccc11d1119d1e..a6187f5fa39f022513afaddf85a232315791db74 100644 (file)
@@ -25,6 +25,7 @@ import org.opendaylight.restconf.handlers.TransactionChainHandler;
 import org.opendaylight.restconf.restful.services.api.RestconfDataService;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.restconf.restful.utils.DeleteDataTransactionUtil;
+import org.opendaylight.restconf.restful.utils.PatchDataTransactionUtil;
 import org.opendaylight.restconf.restful.utils.PostDataTransactionUtil;
 import org.opendaylight.restconf.restful.utils.PutDataTransactionUtil;
 import org.opendaylight.restconf.restful.utils.ReadDataTransactionUtil;
@@ -132,7 +133,7 @@ public class RestconfDataServiceImpl implements RestconfDataService {
                 schemaContextRef.get());
 
         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
-        DOMDataReadWriteTransaction transaction = null;
+        final DOMDataReadWriteTransaction transaction;
         if (mountPoint == null) {
             transaction = this.transactionChainHandler.get().newReadWriteTransaction();
         } else {
@@ -146,22 +147,36 @@ public class RestconfDataServiceImpl implements RestconfDataService {
 
     @Override
     public PATCHStatusContext patchData(final String identifier, final PATCHContext context, final UriInfo uriInfo) {
-        throw new UnsupportedOperationException("Not yet implemented.");
+        Preconditions.checkNotNull(identifier);
+        return patchData(context, uriInfo);
     }
 
     @Override
     public PATCHStatusContext patchData(final PATCHContext context, final UriInfo uriInfo) {
-        throw new UnsupportedOperationException("Not yet implemented.");
+        Preconditions.checkNotNull(context);
+        final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint();
+
+        final DOMDataReadWriteTransaction transaction;
+        final SchemaContextRef ref;
+        if (mountPoint == null) {
+            transaction = this.transactionChainHandler.get().newReadWriteTransaction();
+            ref = new SchemaContextRef(this.schemaContextHandler.get());
+        } else {
+            transaction = transactionOfMountPoint(mountPoint);
+            ref = new SchemaContextRef(mountPoint.getSchemaContext());
+        }
+
+        final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+                context.getInstanceIdentifierContext(), mountPoint, transaction);
+
+        return PatchDataTransactionUtil.patchData(context, transactionNode, ref);
     }
 
     /**
      * Prepare transaction to read data of mount point, if these data are
      * present.
      * @param mountPoint
-     *
-     * @param transactionNode
-     *            - {@link TransactionVarsWrapper} - wrapper for variables
-     * @return {@link NormalizedNode}
+     * @return {@link DOMDataReadWriteTransaction}
      */
     private static DOMDataReadWriteTransaction transactionOfMountPoint(final DOMMountPoint mountPoint) {
         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
index d23f6927234599d1c9d4d4f08048e729c7c5220d..53c19c5fe96fce213f6a983f777b5c43733ea8d9 100644 (file)
@@ -11,7 +11,7 @@ import com.google.common.util.concurrent.CheckedFuture;
 import javax.ws.rs.core.Response;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 
@@ -32,7 +32,6 @@ public final class DeleteDataTransactionUtil {
      *            - Wrapper for data of transaction
      * @return {@link Response}
      */
-
     public static Response deleteData(final TransactionVarsWrapper transactionNode) {
         final CheckedFuture<Void, TransactionCommitFailedException> future = submitData(
                 transactionNode.getTransaction(), transactionNode.getInstanceIdentifier().getInstanceIdentifier());
@@ -42,17 +41,19 @@ public final class DeleteDataTransactionUtil {
     }
 
     /**
-     * Delete data via transaction
+     * Delete data via transaction. Return error if data to delete does not exist.
      *
-     * @param writeTx
-     *            - write transaction
+     * @param readWriteTx
+     *            - read and write transaction
      * @param path
      *            - path of data to delete
      * @return {@link CheckedFuture}
      */
     private static CheckedFuture<Void, TransactionCommitFailedException> submitData(
-            final DOMDataWriteTransaction writeTx, final YangInstanceIdentifier path) {
-        writeTx.delete(LogicalDatastoreType.CONFIGURATION, path);
-        return writeTx.submit();
+            final DOMDataReadWriteTransaction readWriteTx, final YangInstanceIdentifier path) {
+        TransactionUtil.checkItemExists(readWriteTx, LogicalDatastoreType.CONFIGURATION, path,
+                RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE);
+        readWriteTx.delete(LogicalDatastoreType.CONFIGURATION, path);
+        return readWriteTx.submit();
     }
 }
index 8094087ac2832422b6475e9be978c8c5dd4ccf40..0a83ab1a1e04c3af98ab7ef3266442eeeac7bd95 100644 (file)
@@ -45,8 +45,8 @@ final class FutureCallbackTx {
 
             @Override
             public void onFailure(final Throwable t) {
-                handlingLoggerAndValues(t, txType, null, null);
                 responseWaiter.countDown();
+                handlingLoggerAndValues(t, txType, null, null);
             }
 
             @Override
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtil.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtil.java
new file mode 100644 (file)
index 0000000..09cdf6a
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * 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.utils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
+import java.util.List;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
+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.common.references.SchemaContextRef;
+import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant.PatchData;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+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.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class PatchDataTransactionUtil {
+    private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
+
+    public PatchDataTransactionUtil() {
+        throw new UnsupportedOperationException("Util class.");
+    }
+
+    /**
+     * Process edit operations of one {@link PATCHContext}.
+     * @param context Patch context to be processed
+     * @param transactionNode Wrapper for transaction
+     * @param schemaContextRef Soft reference for global schema context
+     * @return {@link PATCHStatusContext}
+     */
+    public static PATCHStatusContext patchData(final PATCHContext context, final TransactionVarsWrapper transactionNode,
+                                               final SchemaContextRef schemaContextRef) {
+        final List<PATCHStatusEntity> editCollection = new ArrayList<>();
+        int errorCounter = 0;
+
+        for (final PATCHEntity patchEntity : context.getData()) {
+            final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
+
+            switch (operation) {
+                case CREATE:
+                    if (errorCounter == 0) {
+                        try {
+                            createDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+                                    patchEntity.getTargetNode(), patchEntity.getNode(),
+                                    transactionNode.getTransaction(), schemaContextRef);
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                                    false, Lists.newArrayList(e.getErrors())));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case DELETE:
+                    if (errorCounter == 0) {
+                        try {
+                            deleteDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+                                    patchEntity.getTargetNode(), transactionNode.getTransaction());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                                    false, Lists.newArrayList(e.getErrors())));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case MERGE:
+                    if (errorCounter == 0) {
+                        try {
+                            mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+                                    patchEntity.getTargetNode(), patchEntity.getNode(), transactionNode.getTransaction(),
+                                    schemaContextRef);
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                                    false, Lists.newArrayList(e.getErrors())));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case REPLACE:
+                    if (errorCounter == 0) {
+                        try {
+                            replaceDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+                                    patchEntity.getTargetNode(), patchEntity.getNode(), schemaContextRef,
+                                    transactionNode.getTransaction());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                                    false, Lists.newArrayList(e.getErrors())));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case REMOVE:
+                    if (errorCounter == 0) {
+                        try {
+                            removeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+                                    patchEntity.getTargetNode(), transactionNode.getTransaction());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                                    false, Lists.newArrayList(e.getErrors())));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                default:
+                    editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(),
+                            false, Lists.newArrayList(new RestconfError(ErrorType.PROTOCOL,
+                            ErrorTag.OPERATION_NOT_SUPPORTED, "Not supported Yang PATCH operation"))));
+                    errorCounter++;
+                    break;
+            }
+        }
+
+        // if no errors then submit transaction, otherwise cancel
+        if (errorCounter == 0) {
+            final ResponseFactory response = new ResponseFactory();
+            final CheckedFuture<Void, TransactionCommitFailedException> future = transactionNode
+                    .getTransaction().submit();
+
+            try {
+                FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response);
+            } catch (final RestconfDocumentedException e) {
+                // if errors occurred during transaction commit then patch failed and global errors are reported
+                return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
+                        Lists.newArrayList(e.getErrors()));
+            }
+
+            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null);
+        } else {
+            transactionNode.getTransaction().cancel();
+            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
+        }
+    }
+
+    /**
+     * Create data within one transaction, return error if already exists.
+     * @param dataStore Datastore to write data to
+     * @param path Path for data to be created
+     * @param payload Data to be created
+     * @param rWTransaction Transaction
+     * @param schemaContextRef Soft reference for global schema context
+     */
+    private static void createDataWithinTransaction(final LogicalDatastoreType dataStore,
+                                                    final YangInstanceIdentifier path,
+                                                    final NormalizedNode<?, ?> payload,
+                                                    final DOMDataReadWriteTransaction rWTransaction,
+                                                    final SchemaContextRef schemaContextRef) {
+        LOG.trace("POST {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
+        createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, true);
+    }
+
+    /**
+     * Check if data exists and remove it within one transaction.
+     * @param dataStore Datastore to delete data from
+     * @param path Path for data to be deleted
+     * @param readWriteTransaction Transaction
+     */
+    private static void deleteDataWithinTransaction(final LogicalDatastoreType dataStore,
+                                                    final YangInstanceIdentifier path,
+                                                    final DOMDataReadWriteTransaction readWriteTransaction) {
+        LOG.trace("Delete {} within Restconf PATCH: {}", dataStore.name(), path);
+        TransactionUtil.checkItemExists(readWriteTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
+        readWriteTransaction.delete(dataStore, path);
+    }
+
+    /**
+     * Merge data within one transaction.
+     * @param dataStore Datastore to merge data to
+     * @param path Path for data to be merged
+     * @param payload Data to be merged
+     * @param writeTransaction Transaction
+     * @param schemaContextRef Soft reference for global schema context
+     */
+    private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
+                                                   final YangInstanceIdentifier path,
+                                                   final NormalizedNode<?, ?> payload,
+                                                   final DOMDataReadWriteTransaction writeTransaction,
+                                                   final SchemaContextRef schemaContextRef) {
+        LOG.trace("Merge {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
+        TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
+
+        // merging is necessary only for lists otherwise we can call put method
+        if (payload instanceof MapNode) {
+            writeTransaction.merge(dataStore, path, payload);
+        } else {
+            writeTransaction.put(dataStore, path, payload);
+        }
+    }
+
+    /**
+     * Do NOT check if data exists and remove it within one transaction.
+     * @param dataStore Datastore to delete data from
+     * @param path Path for data to be deleted
+     * @param writeTransaction Transaction
+     */
+    private static void removeDataWithinTransaction(final LogicalDatastoreType dataStore,
+                                                    final YangInstanceIdentifier path,
+                                                    final DOMDataWriteTransaction writeTransaction) {
+        LOG.trace("Remove {} within Restconf PATCH: {}", dataStore.name(), path);
+        writeTransaction.delete(dataStore, path);
+    }
+
+    /**
+     * Create data within one transaction, replace if already exists.
+     * @param dataStore Datastore to write data to
+     * @param path Path for data to be created
+     * @param payload Data to be created
+     * @param schemaContextRef Soft reference for global schema context
+     * @param rWTransaction Transaction
+     */
+    private static void replaceDataWithinTransaction(final LogicalDatastoreType dataStore,
+                                                     final YangInstanceIdentifier path,
+                                                     final NormalizedNode<?, ?> payload,
+                                                     final SchemaContextRef schemaContextRef,
+                                                     final DOMDataReadWriteTransaction rWTransaction) {
+        LOG.trace("PUT {} within Restconf PATCH: {} with payload {}", dataStore.name(), path, payload);
+        createData(payload, schemaContextRef.get(), path, rWTransaction, dataStore, false);
+    }
+
+    /**
+     * Create data within one transaction. If {@code errorIfExists} is set to {@code true} then data will be checked
+     * for existence before created, otherwise they will be overwritten.
+     * @param payload Data to be created
+     * @param schemaContext Global schema context
+     * @param path Path for data to be created
+     * @param rWTransaction Transaction
+     * @param dataStore Datastore to write data to
+     * @param errorIfExists Enable checking for existence of data (throws error if already exists)
+     */
+    private static void createData(final NormalizedNode<?, ?> payload, final SchemaContext schemaContext,
+                                   final YangInstanceIdentifier path, final DOMDataReadWriteTransaction rWTransaction,
+                                   final LogicalDatastoreType dataStore, final boolean errorIfExists) {
+        if (payload instanceof MapNode) {
+            final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+            rWTransaction.merge(dataStore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+            TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
+            for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+                final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+
+                if (errorIfExists) {
+                    TransactionUtil.checkItemDoesNotExists(
+                            rWTransaction, dataStore, childPath, PatchData.PATCH_TX_TYPE);
+                }
+
+                rWTransaction.put(dataStore, childPath, child);
+            }
+        } else {
+            if (errorIfExists) {
+                TransactionUtil.checkItemDoesNotExists(
+                        rWTransaction, dataStore, path, PatchData.PATCH_TX_TYPE);
+            }
+
+            TransactionUtil.ensureParentsByMerge(path, schemaContext, rWTransaction);
+            rWTransaction.put(dataStore, path, payload);
+        }
+    }
+}
index 5a762ebb7b2878fccaaa3addcb780c31145d2b7d..778acd9734df942d004e55a8c8c67c37ec4966a9 100644 (file)
@@ -8,9 +8,7 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.ListenableFuture;
 import java.net.URI;
-import java.util.concurrent.ExecutionException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -18,9 +16,6 @@ import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
@@ -136,32 +131,11 @@ public final class PostDataTransactionUtil {
     private static void putChild(final NormalizedNode<?, ?> child, final DOMDataReadWriteTransaction readWriteTx,
             final YangInstanceIdentifier path) {
         final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
-        checkItemDesNotExits(childPath, readWriteTx);
+        TransactionUtil.checkItemDoesNotExists(readWriteTx, LogicalDatastoreType.CONFIGURATION, childPath,
+                RestconfDataServiceConstant.PostData.POST_TX_TYPE);
         readWriteTx.put(LogicalDatastoreType.CONFIGURATION, childPath, child);
     }
 
-    /**
-     * Check if data posted to create doesn't exits.
-     *
-     * @param path
-     *            - path to data
-     * @param readWriteTx
-     *            - read write transaction
-     */
-    private static void checkItemDesNotExits(final YangInstanceIdentifier path,
-            final DOMDataReadWriteTransaction readWriteTx) {
-        final ListenableFuture<Boolean> existData = readWriteTx.exists(LogicalDatastoreType.CONFIGURATION, path);
-        try {
-            if (existData.get()) {
-                readWriteTx.cancel();
-                throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
-                        ErrorTag.DATA_EXISTS);
-            }
-        } catch (InterruptedException | ExecutionException e) {
-            LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
-        }
-    }
-
     /**
      * Get location from {@link YangInstanceIdentifier} and {@link UriInfo}
      *
@@ -187,4 +161,3 @@ public final class PostDataTransactionUtil {
         return uriBuilder.build();
     }
 }
-
index 3c82c207e2325523c2152755e5fe1c1a630fcd5b..425f23bf702ed4c313c9791698669ac45c2b8d20 100644 (file)
@@ -10,6 +10,7 @@ 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 javax.annotation.Nonnull;
 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;
@@ -116,6 +117,25 @@ public final class ReadDataTransactionUtil {
         transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
         final NormalizedNode<?, ?> configDataNode = readDataViaTransaction(transactionNode);
 
+        // 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 config data
+        if (stateDataNode == null) {
+            return configDataNode;
+        }
+
+        // return state data
+        if (configDataNode == null) {
+            return stateDataNode;
+        }
+
+        // merge data from config and state
         return mapNode(stateDataNode, configDataNode);
     }
 
@@ -267,8 +287,8 @@ public final class ReadDataTransactionUtil {
      * @param configDataNode
      *            - data node of config data
      */
-    private static void validPossibilityOfMergeNodes(final NormalizedNode<?, ?> stateDataNode,
-            final NormalizedNode<?, ?> configDataNode) {
+    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) {
index 0045ade5348b46e1744b075520c7b50ecde862cd..bc9f4d64633708db728cd9edc5844df8fbd1ad78 100644 (file)
@@ -91,4 +91,16 @@ public final class RestconfDataServiceConstant {
             throw new UnsupportedOperationException("Util class.");
         }
     }
+
+    /**
+     * Constants for data to yang patch
+     *
+     */
+    public final class PatchData {
+        public static final String PATCH_TX_TYPE = "PATCH";
+
+        private PatchData() {
+            throw new UnsupportedOperationException("Util class.");
+        }
+    }
 }
index a05aea193879a628ffa7128ce63bf62f88e6debf..6f63088d50085dd2806498ea3807719965dbe31d 100644 (file)
@@ -8,11 +8,17 @@
 package org.opendaylight.restconf.restful.utils;
 
 import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@@ -20,6 +26,8 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Util class for common methods of transactions
@@ -27,6 +35,8 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
  */
 public final class TransactionUtil {
 
+    private final static Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
+
     private TransactionUtil() {
         throw new UnsupportedOperationException("Util class");
     }
@@ -74,4 +84,52 @@ public final class TransactionUtil {
             writeTx.merge(LogicalDatastoreType.CONFIGURATION, rootNormalizedPath, parentStructure);
         }
     }
+
+    /**
+     * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+     * data does NOT already exists.
+     * @param rWTransaction Transaction
+     * @param store Datastore
+     * @param path Path to be checked
+     * @param operationType Type of operation (READ, POST, PUT, DELETE...)
+     */
+    public static void checkItemExists(final DOMDataReadWriteTransaction rWTransaction,
+                                       final LogicalDatastoreType store, final YangInstanceIdentifier path,
+                                       final String operationType) {
+        final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
+        final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+        FutureCallbackTx.addCallback(future, operationType, response);
+
+        if (!response.result) {
+            final String errMsg = "Operation via Restconf was not executed because data does not exist";
+            LOG.trace("{}:{}", errMsg, path);
+            throw new RestconfDocumentedException(
+                    "Data does not exist", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, path);
+        }
+    }
+
+    /**
+     * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if
+     * data already exists.
+     * @param rWTransaction Transaction
+     * @param store Datastore
+     * @param path Path to be checked
+     * @param operationType Type of operation (READ, POST, PUT, DELETE...)
+     */
+    public static void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,
+                                              final LogicalDatastoreType store, final YangInstanceIdentifier path,
+                                              final String operationType) {
+        final CheckedFuture<Boolean, ReadFailedException> future = rWTransaction.exists(store, path);
+        final FutureDataFactory<Boolean> response = new FutureDataFactory<>();
+
+        FutureCallbackTx.addCallback(future, operationType, response);
+
+        if (response.result) {
+            final String errMsg = "Operation via Restconf was not executed because data already exists";
+            LOG.trace("{}:{}", errMsg, path);
+            throw new RestconfDocumentedException(
+                    "Data already exists", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, path);
+        }
+    }
 }
index 9b9b8d33c43514291fb8e84542c2d64f010455e5..d24c3deb07336c99016c453b3810d1cf44f556d9 100644 (file)
@@ -14,6 +14,7 @@ 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;
@@ -55,10 +56,10 @@ public final class ParserIdentifier {
      *            - {@link SchemaContext}
      * @return {@link InstanceIdentifierContext}
      */
-    public static InstanceIdentifierContext<?> toInstanceIdentifier(final String identifier,
+    public static InstanceIdentifierContext<?> toInstanceIdentifier(@Nullable final String identifier,
             final SchemaContext schemaContext) {
         final YangInstanceIdentifier deserialize;
-        if (identifier.contains(RestconfConstants.MOUNT)) {
+        if (identifier != null && identifier.contains(RestconfConstants.MOUNT)) {
             final String mountPointId = identifier.substring(0, identifier.indexOf("/" + RestconfConstants.MOUNT));
             deserialize = IdentifierCodec.deserialize(mountPointId, schemaContext);
         } else {
@@ -155,11 +156,18 @@ public final class ParserIdentifier {
             final StringBuilder pathBuilder = new StringBuilder();
             while (componentIter.hasNext()) {
                 final String current = componentIter.next();
-                pathBuilder.append("/");
-                pathBuilder.append(current);
+
                 if (RestconfConstants.MOUNT.equals(current)) {
+                    pathBuilder.append("/");
+                    pathBuilder.append(RestconfConstants.MOUNT);
                     break;
                 }
+
+                if (pathBuilder.length() != 0) {
+                    pathBuilder.append("/");
+                }
+
+                pathBuilder.append(current);
             }
             final InstanceIdentifierContext<?> point = ParserIdentifier
                     .toInstanceIdentifier(pathBuilder.toString(), schemaContext);
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11AbstractIdentifierAwareJaxRsProvider.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11AbstractIdentifierAwareJaxRsProvider.java
new file mode 100644 (file)
index 0000000..625c837
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.utils.patch;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.restconf.utils.parser.ParserIdentifier;
+
+public class Draft11AbstractIdentifierAwareJaxRsProvider {
+
+    private static final String POST = "POST";
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private Request request;
+
+    protected final String getIdentifier() {
+        return this.uriInfo.getPathParameters(false).getFirst(RestconfConstants.IDENTIFIER);
+    }
+
+    protected InstanceIdentifierContext<?> getInstanceIdentifierContext() {
+        return ParserIdentifier.toInstanceIdentifier(getIdentifier(),
+                ControllerContext.getInstance().getGlobalSchema());
+    }
+
+    protected UriInfo getUriInfo() {
+        return this.uriInfo;
+    }
+
+    protected boolean isPost() {
+        return POST.equals(this.request.getMethod());
+    }
+}
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11JsonToPATCHBodyReader.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11JsonToPATCHBodyReader.java
new file mode 100644 (file)
index 0000000..c8d034d
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * 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.utils.patch;
+
+import static org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation.isPatchOperationWithValue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.restconf.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({Draft11.MediaTypes.PATCH + RestconfConstants.JSON})
+public class Draft11JsonToPATCHBodyReader extends Draft11AbstractIdentifierAwareJaxRsProvider
+        implements MessageBodyReader<PATCHContext> {
+
+    private final static Logger LOG = LoggerFactory.getLogger(Draft11JsonToPATCHBodyReader.class);
+    private String patchId;
+
+    @Override
+    public boolean isReadable(final Class<?> type, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
+        return true;
+    }
+
+    @Override
+    public PATCHContext readFrom(final Class<PATCHContext> type, final Type genericType,
+                                 final Annotation[] annotations, final MediaType mediaType,
+                                 final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+        try {
+            return readFrom(getInstanceIdentifierContext(), entityStream);
+        } catch (final Exception e) {
+            throw propagateExceptionAs(e);
+        }
+    }
+
+    private static RuntimeException propagateExceptionAs(final Exception e) throws RestconfDocumentedException {
+        if (e instanceof RestconfDocumentedException) {
+            throw (RestconfDocumentedException)e;
+        }
+
+        if (e instanceof ResultAlreadySetException) {
+            LOG.debug("Error parsing json input:", e);
+            throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+        }
+
+        throw new RestconfDocumentedException("Error parsing json input: " + e.getMessage(), ErrorType.PROTOCOL,
+                ErrorTag.MALFORMED_MESSAGE, e);
+    }
+
+    public PATCHContext readFrom(final String uriPath, final InputStream entityStream) throws
+            RestconfDocumentedException {
+        try {
+            return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream);
+        } catch (final Exception e) {
+            propagateExceptionAs(e);
+            return null; // no-op
+        }
+    }
+
+    private PATCHContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream)
+            throws IOException {
+        if (entityStream.available() < 1) {
+            return new PATCHContext(path, null, null);
+        }
+
+        final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+        final List<PATCHEntity> resultList = read(jsonReader, path);
+        jsonReader.close();
+
+        return new PATCHContext(path, resultList, patchId);
+    }
+
+    private List<PATCHEntity> read(final JsonReader in, final InstanceIdentifierContext path) throws IOException {
+        final List<PATCHEntity> resultCollection = new ArrayList<>();
+        final Draft11StringModuleInstanceIdentifierCodec codec = new Draft11StringModuleInstanceIdentifierCodec(
+                path.getSchemaContext());
+        final Draft11JsonToPATCHBodyReader.PatchEdit edit = new Draft11JsonToPATCHBodyReader.PatchEdit();
+
+        while (in.hasNext()) {
+            switch (in.peek()) {
+                case STRING:
+                case NUMBER:
+                    in.nextString();
+                    break;
+                case BOOLEAN:
+                    Boolean.toString(in.nextBoolean());
+                    break;
+                case NULL:
+                    in.nextNull();
+                    break;
+                case BEGIN_ARRAY:
+                    in.beginArray();
+                    break;
+                case BEGIN_OBJECT:
+                    in.beginObject();
+                    break;
+                case END_DOCUMENT:
+                    break;
+                case NAME:
+                    parseByName(in.nextName(), edit, in, path, codec, resultCollection);
+                    break;
+                case END_OBJECT:
+                    in.endObject();
+                    break;
+                case END_ARRAY:
+                    in.endArray();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return ImmutableList.copyOf(resultCollection);
+    }
+
+    /**
+     * Switch value of parsed JsonToken.NAME and read edit definition or patch id
+     * @param name value of token
+     * @param edit PatchEdit instance
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext context
+     * @param codec Draft11StringModuleInstanceIdentifierCodec codec
+     * @param resultCollection collection of parsed edits
+     * @throws IOException
+     */
+    private void parseByName(@Nonnull final String name, @Nonnull final PatchEdit edit,
+                             @Nonnull final JsonReader in, @Nonnull final InstanceIdentifierContext path,
+                             @Nonnull final Draft11StringModuleInstanceIdentifierCodec codec,
+                             @Nonnull final List<PATCHEntity> resultCollection) throws IOException {
+        switch (name) {
+            case "edit" :
+                if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                    in.beginArray();
+
+                    while (in.hasNext()) {
+                        readEditDefinition(edit, in, path, codec);
+                        resultCollection.add(prepareEditOperation(edit));
+                        edit.clear();
+                    }
+
+                    in.endArray();
+                } else {
+                    readEditDefinition(edit, in, path, codec);
+                    resultCollection.add(prepareEditOperation(edit));
+                    edit.clear();
+                }
+
+                break;
+            case "patch-id" :
+                this.patchId = in.nextString();
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Read one patch edit object from Json input
+     * @param edit PatchEdit instance to be filled with read data
+     * @param in JsonReader reader
+     * @param path InstanceIdentifierContext path context
+     * @param codec Draft11StringModuleInstanceIdentifierCodec codec
+     * @throws IOException
+     */
+    private void readEditDefinition(@Nonnull final PatchEdit edit, @Nonnull final JsonReader in,
+                                    @Nonnull final InstanceIdentifierContext path,
+                                    @Nonnull final Draft11StringModuleInstanceIdentifierCodec codec) throws IOException {
+        final StringBuffer value = new StringBuffer();
+        in.beginObject();
+
+        while (in.hasNext()) {
+            final String editDefinition = in.nextName();
+            switch (editDefinition) {
+                case "edit-id" :
+                    edit.setId(in.nextString());
+                    break;
+                case "operation" :
+                    edit.setOperation(in.nextString());
+                    break;
+                case "target" :
+                    // target can be specified completely in request URI
+                    final String target = in.nextString();
+                    if (target.equals("/")) {
+                        edit.setTarget(path.getInstanceIdentifier());
+                        edit.setTargetSchemaNode(path.getSchemaContext());
+                    } else {
+                        edit.setTarget(codec.deserialize(codec.serialize(path.getInstanceIdentifier()).concat(target)));
+                        edit.setTargetSchemaNode(SchemaContextUtil.findDataSchemaNode(path.getSchemaContext(),
+                                codec.getDataContextTree().getChild(edit.getTarget()).getDataSchemaNode().getPath()
+                                        .getParent()));
+                    }
+
+                    break;
+                case "value" :
+                    // save data defined in value node for next (later) processing, because target needs to be read
+                    // always first and there is no ordering in Json input
+                    readValueNode(value, in);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        in.endObject();
+
+        // read saved data to normalized node when target schema is already known
+        edit.setData(readEditData(new JsonReader(new StringReader(value.toString())), edit.getTargetSchemaNode(), path));
+    }
+
+    /**
+     * Parse data defined in value node and saves it to buffer
+     * @param value Buffer to read value node
+     * @param in JsonReader reader
+     * @throws IOException
+     */
+    private void readValueNode(@Nonnull final StringBuffer value, @Nonnull final JsonReader in) throws IOException {
+        in.beginObject();
+        value.append("{");
+
+        value.append("\"" + in.nextName() + "\"" + ":");
+
+        if (in.peek() == JsonToken.BEGIN_ARRAY) {
+            in.beginArray();
+            value.append("[");
+
+            while (in.hasNext()) {
+                readValueObject(value, in);
+                if (in.peek() != JsonToken.END_ARRAY) {
+                    value.append(",");
+                }
+            }
+
+            in.endArray();
+            value.append("]");
+        } else {
+            readValueObject(value, in);
+        }
+
+        in.endObject();
+        value.append("}");
+    }
+
+    /**
+     * Parse one value object of data and saves it to buffer
+     * @param value Buffer to read value object
+     * @param in JsonReader reader
+     * @throws IOException
+     */
+    private void readValueObject(@Nonnull final StringBuffer value, @Nonnull final JsonReader in) throws IOException {
+        in.beginObject();
+        value.append("{");
+
+        while (in.hasNext()) {
+            value.append("\"" + in.nextName() + "\"");
+            value.append(":");
+
+            if (in.peek() == JsonToken.STRING) {
+                value.append("\"" + in.nextString() + "\"");
+            } else {
+                if (in.peek() == JsonToken.BEGIN_ARRAY) {
+                    in.beginArray();
+                    value.append("[");
+
+                    while (in.hasNext()) {
+                        readValueObject(value, in);
+                        if (in.peek() != JsonToken.END_ARRAY) {
+                            value.append(",");
+                        }
+                    }
+
+                    in.endArray();
+                    value.append("]");
+                } else {
+                    readValueObject(value, in);
+                }
+            }
+
+            if (in.peek() != JsonToken.END_OBJECT) {
+                value.append(",");
+            }
+        }
+
+        in.endObject();
+        value.append("}");
+    }
+
+    /**
+     * Read patch edit data defined in value node to NormalizedNode
+     * @param in reader JsonReader reader
+     * @return NormalizedNode representing data
+     */
+    private NormalizedNode readEditData(@Nonnull final JsonReader in, @Nonnull SchemaNode targetSchemaNode,
+                                        @Nonnull InstanceIdentifierContext path) {
+        final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+        final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+        JsonParserStream.create(writer, path.getSchemaContext(), targetSchemaNode).parse(in);
+
+        return resultHolder.getResult();
+    }
+
+    /**
+     * Prepare PATCHEntity from PatchEdit instance when it satisfies conditions, otherwise throws exception
+     * @param edit Instance of PatchEdit
+     * @return PATCHEntity
+     */
+    private PATCHEntity prepareEditOperation(@Nonnull final PatchEdit edit) {
+        if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
+                && checkDataPresence(edit.getOperation(), (edit.getData() != null))) {
+            if (isPatchOperationWithValue(edit.getOperation())) {
+                // for lists allow to manipulate with list items through their parent
+                final YangInstanceIdentifier targetNode;
+                if (edit.getTarget().getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                    targetNode = edit.getTarget().getParent();
+                } else {
+                    targetNode = edit.getTarget();
+                }
+
+                return new PATCHEntity(edit.getId(), edit.getOperation(), targetNode, edit.getData());
+            } else {
+                return new PATCHEntity(edit.getId(), edit.getOperation(), edit.getTarget());
+            }
+        }
+
+        throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+    }
+
+    /**
+     * Check if data is present when operation requires it and not present when operation data is not allowed
+     * @param operation Name of operation
+     * @param hasData Data in edit are present/not present
+     * @return true if data is present when operation requires it or if there are no data when operation does not
+     * allow it, false otherwise
+     */
+    private boolean checkDataPresence(@Nonnull final String operation, final boolean hasData) {
+        if (isPatchOperationWithValue(operation)) {
+            if (hasData) {
+                return true;
+            } else {
+                return false;
+            }
+        } else  {
+            if (!hasData) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Helper class representing one patch edit
+     */
+    private static final class PatchEdit {
+        private String id;
+        private String operation;
+        private YangInstanceIdentifier target;
+        private SchemaNode targetSchemaNode;
+        private NormalizedNode data;
+
+        public String getId() {
+            return id;
+        }
+
+        public void setId(String id) {
+            this.id = id;
+        }
+
+        public String getOperation() {
+            return operation;
+        }
+
+        public void setOperation(String operation) {
+            this.operation = operation;
+        }
+
+        public YangInstanceIdentifier getTarget() {
+            return target;
+        }
+
+        public void setTarget(YangInstanceIdentifier target) {
+            this.target = target;
+        }
+
+        public SchemaNode getTargetSchemaNode() {
+            return targetSchemaNode;
+        }
+
+        public void setTargetSchemaNode(SchemaNode targetSchemaNode) {
+            this.targetSchemaNode = targetSchemaNode;
+        }
+
+        public NormalizedNode getData() {
+            return data;
+        }
+
+        public void setData(NormalizedNode data) {
+            this.data = data;
+        }
+
+        public void clear() {
+            id = null;
+            operation = null;
+            target = null;
+            targetSchemaNode = null;
+            data = null;
+        }
+    }
+}
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11StringModuleInstanceIdentifierCodec.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11StringModuleInstanceIdentifierCodec.java
new file mode 100644 (file)
index 0000000..c4cca11
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.utils.patch;
+
+import com.google.common.base.Preconditions;
+import java.net.URI;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+final class Draft11StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+
+    private final DataSchemaContextTree dataContextTree;
+    private final SchemaContext context;
+    private final String defaultPrefix;
+
+    Draft11StringModuleInstanceIdentifierCodec(SchemaContext context) {
+        this.context = Preconditions.checkNotNull(context);
+        this.dataContextTree = DataSchemaContextTree.from(context);
+        this.defaultPrefix = "";
+    }
+
+    Draft11StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+        this.context = Preconditions.checkNotNull(context);
+        this.dataContextTree = DataSchemaContextTree.from(context);
+        this.defaultPrefix = defaultPrefix;
+    }
+
+    @Override
+    protected Module moduleForPrefix(@Nonnull String prefix) {
+        if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
+            return this.context.findModuleByName(this.defaultPrefix, null);
+        } else {
+            return this.context.findModuleByName(prefix, null);
+        }
+    }
+
+    @Nonnull
+    @Override
+    protected DataSchemaContextTree getDataContextTree() {
+        return this.dataContextTree;
+    }
+
+    @Nullable
+    @Override
+    protected String prefixForNamespace(@Nonnull URI namespace) {
+        final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
+        return module == null ? null : module.getName();
+    }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11XmlToPATCHBodyReader.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft11XmlToPATCHBodyReader.java
new file mode 100644 (file)
index 0000000..4a90d8f
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * 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.utils.patch;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.restconf.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+@Provider
+@Consumes({Draft11.MediaTypes.PATCH + RestconfConstants.XML})
+public class Draft11XmlToPATCHBodyReader extends Draft11AbstractIdentifierAwareJaxRsProvider implements
+        MessageBodyReader<PATCHContext> {
+
+    private final static Logger LOG = LoggerFactory.getLogger(Draft11XmlToPATCHBodyReader.class);
+    private static final DocumentBuilderFactory BUILDERFACTORY;
+
+    static {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try {
+            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+            factory.setXIncludeAware(false);
+            factory.setExpandEntityReferences(false);
+        } catch (final ParserConfigurationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+        factory.setNamespaceAware(true);
+        factory.setCoalescing(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setIgnoringComments(true);
+        BUILDERFACTORY = factory;
+    }
+
+    @Override
+    public boolean isReadable(final Class<?> type, final Type genericType,
+                              final Annotation[] annotations, final MediaType mediaType) {
+        return true;
+    }
+
+    @Override
+    public PATCHContext readFrom(final Class<PATCHContext> type, final Type genericType,
+                                 final Annotation[] annotations, final MediaType mediaType,
+                                 final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+            throws IOException, WebApplicationException {
+
+        try {
+            final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
+
+            if (entityStream.available() < 1) {
+                // represent empty nopayload input
+                return new PATCHContext(path, null, null);
+            }
+
+            final DocumentBuilder dBuilder;
+            try {
+                dBuilder = BUILDERFACTORY.newDocumentBuilder();
+            } catch (final ParserConfigurationException e) {
+                throw new IllegalStateException("Failed to parse XML document", e);
+            }
+            final Document doc = dBuilder.parse(entityStream);
+
+            return parse(path, doc);
+        } catch (final RestconfDocumentedException e) {
+            throw e;
+        } catch (final Exception e) {
+            LOG.debug("Error parsing xml input", e);
+
+            throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+                    ErrorTag.MALFORMED_MESSAGE);
+        }
+    }
+
+    private PATCHContext parse(final InstanceIdentifierContext<?> pathContext, final Document doc) {
+        final List<PATCHEntity> resultCollection = new ArrayList<>();
+        final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
+        final NodeList editNodes = doc.getElementsByTagName("edit");
+        final DomToNormalizedNodeParserFactory parserFactory =
+                DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER,
+                        pathContext.getSchemaContext());
+
+        for (int i = 0; i < editNodes.getLength(); i++) {
+            DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
+            final Element element = (Element) editNodes.item(i);
+            final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue();
+            final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue();
+            final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue();
+            final List<Element> values = readValueNodes(element, operation);
+            final Element firstValueElement = values != null ? values.get(0) : null;
+
+            // get namespace according to schema node from path context or value
+            final String namespace = (firstValueElement == null) ?
+                    schemaNode.getQName().getNamespace().toString() : firstValueElement.getNamespaceURI();
+
+            // find module according to namespace
+            final Module module = pathContext.getSchemaContext().findModuleByNamespace(
+                    URI.create(namespace)).iterator().next();
+
+            // initialize codec + set default prefix derived from module name
+            final Draft11StringModuleInstanceIdentifierCodec codec = new Draft11StringModuleInstanceIdentifierCodec(
+                    pathContext.getSchemaContext(), module.getName());
+
+            // find complete path to target and target schema node
+            // target can be also empty (only slash)
+            YangInstanceIdentifier targetII;
+            final SchemaNode targetNode;
+            if (target.equals("/")) {
+                targetII = pathContext.getInstanceIdentifier();
+                targetNode = pathContext.getSchemaContext();
+            } else {
+                targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier())
+                        .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement,
+                                namespace, module.getQNameModule().getFormattedRevision())));
+
+                targetNode = SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath().getParent());
+
+                // move schema node
+                schemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(),
+                        codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath());
+            }
+
+            if (targetNode == null) {
+                LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode());
+                throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+                        ErrorTag.MALFORMED_MESSAGE);
+            } else {
+                if (PATCHEditOperation.isPatchOperationWithValue(operation)) {
+                    NormalizedNode<?, ?> parsed = null;
+                    if (schemaNode instanceof ContainerSchemaNode) {
+                        parsed = parserFactory.getContainerNodeParser().parse(values, (ContainerSchemaNode) schemaNode);
+                    } else if (schemaNode instanceof ListSchemaNode) {
+                        parsed = parserFactory.getMapNodeParser().parse(values, (ListSchemaNode) schemaNode);
+                    }
+
+                    // for lists allow to manipulate with list items through their parent
+                    if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
+                        targetII = targetII.getParent();
+                    }
+
+                    resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed));
+                } else {
+                    resultCollection.add(new PATCHEntity(editId, operation, targetII));
+                }
+            }
+        }
+
+        return new PATCHContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+    }
+
+    /**
+     * Read value nodes
+     * @param element Element of current edit operation
+     * @param operation Name of current operation
+     * @return List of value elements
+     */
+    private List<Element> readValueNodes(@Nonnull final Element element, @Nonnull final String operation) {
+        final Node valueNode = element.getElementsByTagName("value").item(0);
+
+        if (PATCHEditOperation.isPatchOperationWithValue(operation) && valueNode == null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (!PATCHEditOperation.isPatchOperationWithValue(operation) && valueNode != null) {
+            throw new RestconfDocumentedException("Error parsing input",
+                    ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+
+        if (valueNode == null) {
+            return null;
+        }
+
+        final List<Element> result = new ArrayList<>();
+        final NodeList childNodes = valueNode.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            if (childNodes.item(i) instanceof Element) {
+                result.add((Element) childNodes.item(i));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare non-conditional XPath suitable for deserialization
+     * with {@link Draft11StringModuleInstanceIdentifierCodec}
+     * @param schemaNode Top schema node
+     * @param target Edit operation target
+     * @param value Element with value
+     * @param namespace Module namespace
+     * @param revision Module revision
+     * @return Non-conditional XPath
+     */
+    private String prepareNonCondXpath(@Nonnull final DataSchemaNode schemaNode, @Nonnull final String target,
+                                       @Nonnull final Element value, @Nonnull final String namespace,
+                                       @Nonnull String revision) {
+        final Iterator<String> args = Splitter.on("/").split(target.substring(target.indexOf(':') + 1)).iterator();
+
+        final StringBuilder nonCondXpath = new StringBuilder();
+        SchemaNode childNode = schemaNode;
+
+        while (args.hasNext()) {
+            final String s = args.next();
+            nonCondXpath.append("/");
+            nonCondXpath.append(s);
+            childNode = ((DataNodeContainer) childNode).getDataChildByName(QName.create(namespace, revision, s));
+
+            if (childNode instanceof ListSchemaNode && args.hasNext()) {
+                appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), args);
+            }
+        }
+
+        if (childNode instanceof ListSchemaNode && value != null) {
+            final Iterator<String> keyValues = readKeyValues(value,
+                    ((ListSchemaNode) childNode).getKeyDefinition().iterator());
+            appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), keyValues);
+        }
+
+        return nonCondXpath.toString();
+    }
+
+    /**
+     * Read value for every list key
+     * @param value Value element
+     * @param keys Iterator of list keys names
+     * @return Iterator of list keys values
+     */
+    private Iterator<String> readKeyValues(@Nonnull final Element value, @Nonnull final Iterator<QName> keys) {
+        final List<String> result = new ArrayList<>();
+
+        while (keys.hasNext()) {
+            result.add(value.getElementsByTagName(keys.next().getLocalName()).item(0).getFirstChild().getNodeValue());
+        }
+
+        return result.iterator();
+    }
+
+    /**
+     * Append key name - key value pairs for every list key to {@code nonCondXpath}
+     * @param nonCondXpath Builder for creating non-conditional XPath
+     * @param keyNames Iterator of list keys names
+     * @param keyValues Iterator of list keys values
+     */
+    private void appendKeys(@Nonnull final StringBuilder nonCondXpath, @Nonnull final Iterator<QName> keyNames,
+                            @Nonnull final Iterator<String> keyValues) {
+        while (keyNames.hasNext()) {
+            nonCondXpath.append("[");
+            nonCondXpath.append(keyNames.next().getLocalName());
+            nonCondXpath.append("=");
+            nonCondXpath.append("'");
+            nonCondXpath.append(keyValues.next());
+            nonCondXpath.append("'");
+            nonCondXpath.append("]");
+        }
+    }
+}
index cc1d26fb979ddfd708c1651f86eaadf941bcdf94..8f5088f8bd0c8bf346bb9d131d43f4d5042611d6 100644 (file)
@@ -7,7 +7,7 @@ module sal-remote-augment {
     import sal-remote {prefix salrmt; revision-date "2014-01-14";}
 
     description
-        "Added input parameters to rpc create-data-change-event-subscription";
+        "Added input parameters to rpc create-data-change-event-subscription and to create-notification-stream";
 
     revision "2014-07-08" {
     }
@@ -28,4 +28,15 @@ module sal-remote-augment {
         }
     }
 
+    augment "/salrmt:create-notification-stream/salrmt:input" {
+        leaf notification-output-type {
+            type enumeration {
+                enum JSON;
+                enum XML;
+            }
+            default "XML";
+            description "Input parameter which type of output will be parsed on notification";
+        }
+    }
+
 }
index 6a728dc40c3055ceac51328eb4d40a68a886f54f..ded5e3dc09d477d998da43b9baf1687c84d6e43f 100644 (file)
@@ -14,13 +14,11 @@ import static org.mockito.Mockito.when;
 
 import java.lang.reflect.Field;
 import java.util.Collections;
-
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Request;
 import javax.ws.rs.core.UriInfo;
-
 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
 import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
 import org.opendaylight.netconf.sal.rest.impl.AbstractIdentifierAwareJaxRsProvider;
@@ -29,19 +27,9 @@ import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-/**
- * sal-rest-connector org.opendaylight.controller.sal.rest.impl.test.providers
- *
- *
- *
- * @author <a href="mailto:vdemcak@cisco.com">Vaclav Demcak</a>
- *
- *         Created: Mar 7, 2015
- */
 public abstract class AbstractBodyReaderTest {
 
-    protected final static ControllerContext controllerContext = ControllerContext
-            .getInstance();
+    protected final static ControllerContext controllerContext = ControllerContext.getInstance();
     protected final MediaType mediaType;
     private static Field uriField;
     private static Field requestField;
@@ -69,20 +57,24 @@ public abstract class AbstractBodyReaderTest {
             final boolean isPost) throws NoSuchFieldException,
             SecurityException, IllegalArgumentException, IllegalAccessException {
         final UriInfo uriInfoMock = mock(UriInfo.class);
-        final MultivaluedMap<String, String> pathParm = new MultivaluedHashMap<>(
-                1);
-        pathParm.put(RestconfConstants.IDENTIFIER,
-                Collections.singletonList(identifier));
+        final MultivaluedMap<String, String> pathParm = new MultivaluedHashMap<>(1);
+
+        if (!identifier.isEmpty()) {
+            pathParm.put(RestconfConstants.IDENTIFIER, Collections.singletonList(identifier));
+        }
+
         when(uriInfoMock.getPathParameters()).thenReturn(pathParm);
         when(uriInfoMock.getPathParameters(false)).thenReturn(pathParm);
         when(uriInfoMock.getPathParameters(true)).thenReturn(pathParm);
         uriField.set(normalizedNodeProvider, uriInfoMock);
+
         final Request request = mock(Request.class);
         if (isPost) {
             when(request.getMethod()).thenReturn("POST");
         } else {
             when(request.getMethod()).thenReturn("PUT");
         }
+
         requestField.set(normalizedNodeProvider, request);
     }
 
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/Draft11AbstractBodyReaderTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/Draft11AbstractBodyReaderTest.java
new file mode 100644 (file)
index 0000000..ff7b770
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.rest.api.RestconfConstants;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.restconf.utils.patch.Draft11AbstractIdentifierAwareJaxRsProvider;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public abstract class Draft11AbstractBodyReaderTest {
+
+    protected final static ControllerContext controllerContext = ControllerContext.getInstance();
+    protected final MediaType mediaType;
+    private static Field uriField;
+    private static Field requestField;
+
+    public Draft11AbstractBodyReaderTest() throws NoSuchFieldException,
+            SecurityException {
+        uriField = Draft11AbstractIdentifierAwareJaxRsProvider.class
+                .getDeclaredField("uriInfo");
+        uriField.setAccessible(true);
+        requestField = Draft11AbstractIdentifierAwareJaxRsProvider.class
+                .getDeclaredField("request");
+        requestField.setAccessible(true);
+        mediaType = getMediaType();
+    }
+
+    protected abstract MediaType getMediaType();
+
+    protected static SchemaContext schemaContextLoader(final String yangPath,
+            final SchemaContext schemaContext) {
+        return TestRestconfUtils.loadSchemaContext(yangPath, schemaContext);
+    }
+
+    protected static <T extends Draft11AbstractIdentifierAwareJaxRsProvider> void mockBodyReader(
+            final String identifier, final T normalizedNodeProvider,
+            final boolean isPost) throws NoSuchFieldException,
+            SecurityException, IllegalArgumentException, IllegalAccessException {
+        final UriInfo uriInfoMock = mock(UriInfo.class);
+        final MultivaluedMap<String, String> pathParm = new MultivaluedHashMap<>(1);
+
+        if (!identifier.isEmpty()) {
+            pathParm.put(RestconfConstants.IDENTIFIER, Collections.singletonList(identifier));
+        }
+
+        when(uriInfoMock.getPathParameters()).thenReturn(pathParm);
+        when(uriInfoMock.getPathParameters(false)).thenReturn(pathParm);
+        when(uriInfoMock.getPathParameters(true)).thenReturn(pathParm);
+        uriField.set(normalizedNodeProvider, uriInfoMock);
+
+        final Request request = mock(Request.class);
+        if (isPost) {
+            when(request.getMethod()).thenReturn("POST");
+        } else {
+            when(request.getMethod()).thenReturn("PUT");
+        }
+
+        requestField.set(normalizedNodeProvider, request);
+    }
+
+    protected static void checkMountPointNormalizedNodeContext(
+            final NormalizedNodeContext nnContext) {
+        checkNormalizedNodeContext(nnContext);
+        assertNotNull(nnContext.getInstanceIdentifierContext().getMountPoint());
+    }
+
+    protected static void checkNormalizedNodeContext(
+            final NormalizedNodeContext nnContext) {
+        assertNotNull(nnContext.getData());
+        assertNotNull(nnContext.getInstanceIdentifierContext()
+                .getInstanceIdentifier());
+        assertNotNull(nnContext.getInstanceIdentifierContext()
+                .getSchemaContext());
+        assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaNode());
+    }
+
+    protected static void checkPATCHContext(final PATCHContext patchContext) {
+        assertNotNull(patchContext.getData());
+        assertNotNull(patchContext.getInstanceIdentifierContext().getInstanceIdentifier());
+        assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext());
+        assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode());
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11JsonPATCHBodyReader.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11JsonPATCHBodyReader.java
new file mode 100644 (file)
index 0000000..02acc09
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.restconf.utils.patch.Draft11JsonToPATCHBodyReader;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestDraft11JsonPATCHBodyReader extends Draft11AbstractBodyReaderTest {
+
+    private final Draft11JsonToPATCHBodyReader jsonPATCHBodyReader;
+    private static SchemaContext schemaContext;
+
+    public TestDraft11JsonPATCHBodyReader() throws NoSuchFieldException, SecurityException {
+        super();
+        jsonPATCHBodyReader = new Draft11JsonToPATCHBodyReader();
+    }
+
+    @Override
+    protected MediaType getMediaType() {
+        return new MediaType(APPLICATION_JSON, null);
+    }
+
+    @BeforeClass
+    public static void initialization() {
+        schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+        controllerContext.setSchemas(schemaContext);
+    }
+
+    @Test
+    public void modulePATCHDataTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdata.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of successful PATCH consisting of create and delete PATCH operations.
+     */
+    @Test
+    public void modulePATCHCreateAndDeleteTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueMissingNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueMissing.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueNotSupportedNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnListTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnContainerTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11XmlPATCHBodyReader.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestDraft11XmlPATCHBodyReader.java
new file mode 100644 (file)
index 0000000..a8955fa
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.restconf.utils.patch.Draft11XmlToPATCHBodyReader;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestDraft11XmlPATCHBodyReader extends Draft11AbstractBodyReaderTest {
+
+    private final Draft11XmlToPATCHBodyReader xmlPATCHBodyReader;
+    private static SchemaContext schemaContext;
+
+    public TestDraft11XmlPATCHBodyReader() throws NoSuchFieldException, SecurityException {
+        super();
+        xmlPATCHBodyReader = new Draft11XmlToPATCHBodyReader();
+    }
+
+    @Override
+    protected MediaType getMediaType() {
+        return new MediaType(MediaType.APPLICATION_XML, null);
+    }
+
+    @BeforeClass
+    public static void initialization() throws NoSuchFieldException, SecurityException {
+        schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+        controllerContext.setSchemas(schemaContext);
+    }
+
+    @Test
+    public void moduleDataTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Error code 400 should be returned.
+     */
+    @Test
+    public void moduleDataValueMissingNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Error code 400 should be
+     * returned.
+     */
+    @Test
+    public void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+
+    /**
+     * Test of Yang PATCH with absolute target path.
+     */
+    @Test
+    public void moduleDataAbsoluteTargetPathTest() throws Exception {
+        final String uri = "";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnListTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1=leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnContainerTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+}
index bcceb072c41e7240b07cb50153e671de9afd5915..b999c036cc8ad163906f64bfc510212edeedb3e5 100644 (file)
@@ -9,6 +9,8 @@
 package org.opendaylight.controller.sal.rest.impl.test.providers;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import java.io.InputStream;
 import javax.ws.rs.core.MediaType;
@@ -16,6 +18,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.netconf.sal.rest.impl.JsonToPATCHBodyReader;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public class TestJsonPATCHBodyReader extends AbstractBodyReaderTest {
@@ -51,4 +54,108 @@ public class TestJsonPATCHBodyReader extends AbstractBodyReaderTest {
                 .readFrom(null, null, null, mediaType, null, inputStream);
         checkPATCHContext(returnValue);
     }
+
+    /**
+     * Test of successful PATCH consisting of create and delete PATCH operations.
+     */
+    @Test
+    public void modulePATCHCreateAndDeleteTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueMissingNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueMissing.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueNotSupportedNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnListTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnContainerTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
 }
index 9b98dd8cafe8c0322cd7655af43ca38b205090e2..4768a0c9e8a5375b4568850a2181d2d65a920c61 100644 (file)
@@ -8,12 +8,16 @@
 
 package org.opendaylight.controller.sal.rest.impl.test.providers;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
 import java.io.InputStream;
 import javax.ws.rs.core.MediaType;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.opendaylight.netconf.sal.rest.impl.XmlToPATCHBodyReader;
 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public class TestXmlPATCHBodyReader extends AbstractBodyReaderTest {
@@ -47,4 +51,96 @@ public class TestXmlPATCHBodyReader extends AbstractBodyReaderTest {
                 .readFrom(null, null, null, mediaType, null, inputStream);
         checkPATCHContext(returnValue);
     }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Error code 400 should be returned.
+     */
+    @Test
+    public void moduleDataValueMissingNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Error code 400 should be
+     * returned.
+     */
+    @Test
+    public void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+
+    /**
+     * Test of Yang PATCH with absolute target path.
+     */
+    @Test
+    public void moduleDataAbsoluteTargetPathTest() throws Exception {
+        final String uri = "";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnListTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnContainerTest() throws Exception {
+        final String uri = "instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContext(returnValue);
+    }
 }
index 7078b38ecd951309d3ad26ef67ef61ea301817ba..1b450d482e9817ee6cab191a7199dc3ca45c4a26 100644 (file)
@@ -18,6 +18,7 @@ import com.google.common.util.concurrent.CheckedFuture;
 import java.io.FileNotFoundException;
 import java.net.URI;
 import java.util.List;
+import javax.ws.rs.core.Response.Status;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -26,6 +27,7 @@ import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 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;
@@ -71,8 +73,12 @@ public class RestPutListDataTest {
         restconfImpl = RestconfImpl.getInstance();
         restconfImpl.setBroker(brokerFacade);
         restconfImpl.setControllerContext(controllerContext);
-        when(brokerFacade.commitConfigurationDataPut(any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class)))
-                .thenReturn(mock(CheckedFuture.class));
+        final PutResult result = mock(PutResult.class);
+        when(brokerFacade.commitConfigurationDataPut(any(SchemaContext.class), any(YangInstanceIdentifier.class),
+                any(NormalizedNode.class)))
+                        .thenReturn(result);
+        when(result.getFutureOfPutData()).thenReturn(mock(CheckedFuture.class));
+        when(result.getStatus()).thenReturn(Status.OK);
     }
 
     /**
index 43b3ca7bec977c28e15f6482d28891dfc5d6756e..670377069872328e06e85e80001698b9a2a51246 100644 (file)
@@ -11,20 +11,22 @@ package org.opendaylight.controller.sal.restconf.impl.test;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import java.util.concurrent.Future;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -39,15 +41,20 @@ 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.DOMNotificationService;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 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.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -64,63 +71,47 @@ import org.opendaylight.yangtools.yang.model.api.SchemaPath;
  * @author Thomas Pantelis
  */
 public class BrokerFacadeTest {
-
-    @Mock
-    DOMDataBroker domDataBroker;
-
-    @Mock
-    ConsumerSession context;
-
-    @Mock
-    DOMRpcService mockRpcService;
-
-    @Mock
-    DOMMountPoint mockMountInstance;
-
-    BrokerFacade brokerFacade = BrokerFacade.getInstance();
-
-    NormalizedNode<?, ?> dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces");
-    CheckedFuture<Optional<NormalizedNode<?, ?>>,ReadFailedException> dummyNodeInFuture = wrapDummyNode(dummyNode);
-
-    QName qname = TestUtils.buildQName("interfaces","test:module", "2014-01-09");
-
-    SchemaPath type = SchemaPath.create(true, qname);
-
-    YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build();
-
-    @Mock
-    DOMDataReadOnlyTransaction rTransaction;
-
-    @Mock
-    DOMDataWriteTransaction wTransaction;
-
-    @Mock
-    DOMDataReadWriteTransaction rwTransaction;
+    @Mock private DOMDataBroker domDataBroker;
+    @Mock private DOMNotificationService domNotification;
+    @Mock private ConsumerSession context;
+    @Mock private DOMRpcService mockRpcService;
+    @Mock private DOMMountPoint mockMountInstance;
+
+    private final BrokerFacade brokerFacade = BrokerFacade.getInstance();
+    private final NormalizedNode<?, ?> dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces");
+    private final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> dummyNodeInFuture =
+            wrapDummyNode(dummyNode);
+    private final QName qname = TestUtils.buildQName("interfaces","test:module", "2014-01-09");
+    private final SchemaPath type = SchemaPath.create(true, qname);
+    private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build();
+
+    @Mock private DOMDataReadOnlyTransaction rTransaction;
+    @Mock private DOMDataWriteTransaction wTransaction;
+    @Mock private DOMDataReadWriteTransaction rwTransaction;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        // TODO it is started before every test method
-        brokerFacade.setDomDataBroker(domDataBroker);
-        brokerFacade.setRpcService(mockRpcService);
-        brokerFacade.setContext(context);
-        when(domDataBroker.newReadOnlyTransaction()).thenReturn(rTransaction);
-        when(domDataBroker.newWriteOnlyTransaction()).thenReturn(wTransaction);
-        when(domDataBroker.newReadWriteTransaction()).thenReturn(rwTransaction);
+        this.brokerFacade.setDomDataBroker(this.domDataBroker);
+        this.brokerFacade.setDomNotificationService(this.domNotification);
+        this.brokerFacade.setRpcService(this.mockRpcService);
+        this.brokerFacade.setContext(this.context);
+        when(this.domDataBroker.newReadOnlyTransaction()).thenReturn(this.rTransaction);
+        when(this.domDataBroker.newWriteOnlyTransaction()).thenReturn(this.wTransaction);
+        when(this.domDataBroker.newReadWriteTransaction()).thenReturn(this.rwTransaction);
 
         ControllerContext.getInstance().setSchemas(TestUtils.loadSchemaContext("/full-versions/test-module"));
-
     }
 
-    private CheckedFuture<Optional<NormalizedNode<?, ?>>,ReadFailedException> wrapDummyNode(final NormalizedNode<?, ?> dummyNode) {
-        return  Futures.immediateCheckedFuture(Optional.<NormalizedNode<?, ?>> of(dummyNode));
+    private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> wrapDummyNode(
+            final NormalizedNode<?, ?> dummyNode) {
+        return Futures.immediateCheckedFuture(Optional.<NormalizedNode<?, ?>> of(dummyNode));
     }
 
-    private CheckedFuture<Boolean,ReadFailedException> wrapExistence(final Boolean exists) {
-        return  Futures.immediateCheckedFuture(exists);
+    private CheckedFuture<Boolean, ReadFailedException> wrapExistence(final Boolean exists) {
+        return Futures.immediateCheckedFuture(exists);
     }
 
-
     /**
      * Value of this node shouldn't be important for testing purposes
      */
@@ -131,38 +122,39 @@ public class BrokerFacadeTest {
 
     @Test
     public void testReadConfigurationData() {
-        when(rTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn(
-                dummyNodeInFuture);
+        when(this.rTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn(
+                this.dummyNodeInFuture);
 
-        final NormalizedNode<?, ?> actualNode = brokerFacade.readConfigurationData(instanceID);
+        final NormalizedNode<?, ?> actualNode = this.brokerFacade.readConfigurationData(this.instanceID);
 
-        assertSame("readConfigurationData", dummyNode, actualNode);
+        assertSame("readConfigurationData", this.dummyNode, actualNode);
     }
 
     @Test
     public void testReadOperationalData() {
-        when(rTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn(
-                dummyNodeInFuture);
+        when(this.rTransaction.read(any(LogicalDatastoreType.class), any(YangInstanceIdentifier.class))).thenReturn(
+                this.dummyNodeInFuture);
 
-        final NormalizedNode<?, ?> actualNode = brokerFacade.readOperationalData(instanceID);
+        final NormalizedNode<?, ?> actualNode = this.brokerFacade.readOperationalData(this.instanceID);
 
-        assertSame("readOperationalData", dummyNode, actualNode);
+        assertSame("readOperationalData", this.dummyNode, actualNode);
     }
 
     @Test(expected = RestconfDocumentedException.class)
     public void testReadOperationalDataWithNoDataBroker() {
-        brokerFacade.setDomDataBroker(null);
+        this.brokerFacade.setDomDataBroker(null);
 
-        brokerFacade.readOperationalData(instanceID);
+        this.brokerFacade.readOperationalData(this.instanceID);
     }
 
     @Test
     public void testInvokeRpc() throws Exception {
         final DOMRpcResult expResult = mock(DOMRpcResult.class);
         final CheckedFuture<DOMRpcResult, DOMRpcException> future = Futures.immediateCheckedFuture(expResult);
-        when(mockRpcService.invokeRpc(type, dummyNode)).thenReturn(future);
+        when(this.mockRpcService.invokeRpc(this.type, this.dummyNode)).thenReturn(future);
 
-        final CheckedFuture<DOMRpcResult, DOMRpcException> actualFuture = brokerFacade.invokeRpc(type, dummyNode);
+        final CheckedFuture<DOMRpcResult, DOMRpcException> actualFuture = this.brokerFacade
+                .invokeRpc(this.type, this.dummyNode);
         assertNotNull("Future is null", actualFuture);
         final DOMRpcResult actualResult = actualFuture.get();
         assertSame("invokeRpc", expResult, actualResult);
@@ -170,26 +162,34 @@ public class BrokerFacadeTest {
 
     @Test(expected = RestconfDocumentedException.class)
     public void testInvokeRpcWithNoConsumerSession() {
-        brokerFacade.setContext(null);
-        brokerFacade.invokeRpc(type, dummyNode);
+        this.brokerFacade.setContext(null);
+        this.brokerFacade.invokeRpc(this.type, this.dummyNode);
     }
 
-    @Ignore
     @Test
-    public void testCommitConfigurationDataPut() {
+    public void testCommitConfigurationDataPut() throws Exception {
         @SuppressWarnings("unchecked")
         final CheckedFuture<Void, TransactionCommitFailedException> expFuture = mock(CheckedFuture.class);
+        when(this.rwTransaction.submit()).thenReturn(expFuture);
+
+        final Optional<NormalizedNode<?, ?>> optionalMock = mock(Optional.class);
+        when(optionalMock.get()).thenReturn(null);
 
-        when(wTransaction.submit()).thenReturn(expFuture);
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readFuture = Futures
+                .immediateCheckedFuture(optionalMock);
+        when(this.rwTransaction.read(LogicalDatastoreType.CONFIGURATION, this.instanceID)).thenReturn(readFuture);
 
-        final Future<Void> actualFuture = brokerFacade.commitConfigurationDataPut((SchemaContext)null, instanceID, dummyNode);
+        final PutResult result = this.brokerFacade.commitConfigurationDataPut(mock(SchemaContext.class),
+                this.instanceID, this.dummyNode);
+
+        final Future<Void> actualFuture = result.getFutureOfPutData();
 
         assertSame("commitConfigurationDataPut", expFuture, actualFuture);
 
-        final InOrder inOrder = inOrder(domDataBroker, wTransaction);
-        inOrder.verify(domDataBroker).newWriteOnlyTransaction();
-        inOrder.verify(wTransaction).put(LogicalDatastoreType.CONFIGURATION, instanceID, dummyNode);
-        inOrder.verify(wTransaction).submit();
+        final InOrder inOrder = inOrder(this.domDataBroker, this.rwTransaction);
+        inOrder.verify(this.domDataBroker).newReadWriteTransaction();
+        inOrder.verify(this.rwTransaction).put(LogicalDatastoreType.CONFIGURATION, this.instanceID, this.dummyNode);
+        inOrder.verify(this.rwTransaction).submit();
     }
 
     @Test
@@ -197,76 +197,146 @@ public class BrokerFacadeTest {
         @SuppressWarnings("unchecked")
         final CheckedFuture<Void, TransactionCommitFailedException> expFuture = mock(CheckedFuture.class);
 
-        when(rwTransaction.exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(
-            wrapExistence(false));
-
+        when(this.rwTransaction.exists(LogicalDatastoreType.CONFIGURATION, this.instanceID))
+                .thenReturn(wrapExistence(false));
 
-        when(rwTransaction.submit()).thenReturn(expFuture);
+        when(this.rwTransaction.submit()).thenReturn(expFuture);
 
-        final CheckedFuture<Void, TransactionCommitFailedException> actualFuture = brokerFacade.commitConfigurationDataPost(
-                (SchemaContext)null, instanceID, dummyNode);
+        final CheckedFuture<Void, TransactionCommitFailedException> actualFuture = this.brokerFacade
+                .commitConfigurationDataPost(mock(SchemaContext.class), this.instanceID, this.dummyNode);
 
         assertSame("commitConfigurationDataPost", expFuture, actualFuture);
 
-        final InOrder inOrder = inOrder(domDataBroker, rwTransaction);
-        inOrder.verify(domDataBroker).newReadWriteTransaction();
-        inOrder.verify(rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, instanceID);
-        inOrder.verify(rwTransaction).put(LogicalDatastoreType.CONFIGURATION, instanceID, dummyNode);
-        inOrder.verify(rwTransaction).submit();
+        final InOrder inOrder = inOrder(this.domDataBroker, this.rwTransaction);
+        inOrder.verify(this.domDataBroker).newReadWriteTransaction();
+        inOrder.verify(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.instanceID);
+        inOrder.verify(this.rwTransaction).put(LogicalDatastoreType.CONFIGURATION, this.instanceID, this.dummyNode);
+        inOrder.verify(this.rwTransaction).submit();
     }
 
     @Test(expected = RestconfDocumentedException.class)
     public void testCommitConfigurationDataPostAlreadyExists() {
         final CheckedFuture<Boolean, ReadFailedException> successFuture = Futures.immediateCheckedFuture(Boolean.TRUE);
-        when(rwTransaction.exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(
-                successFuture);
+        when(this.rwTransaction.exists(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class)))
+                .thenReturn(successFuture);
         try {
             // Schema context is only necessary for ensuring parent structure
-            brokerFacade.commitConfigurationDataPost((SchemaContext)null, instanceID, dummyNode);
+            this.brokerFacade.commitConfigurationDataPost((SchemaContext) null, this.instanceID, this.dummyNode);
         } catch (final RestconfDocumentedException e) {
             assertEquals("getErrorTag", RestconfError.ErrorTag.DATA_EXISTS, e.getErrors().get(0).getErrorTag());
             throw e;
         }
     }
 
+    /**
+     * Positive test of delete operation when data to delete exits. Returned value and order of steps are validated.
+     */
     @Test
-    public void testCommitConfigurationDataDelete() {
+    public void testCommitConfigurationDataDelete() throws Exception {
+        // assume that data to delete exists
+        prepareDataForDelete(true);
+
+        // expected result
         @SuppressWarnings("unchecked")
         final CheckedFuture<Void, TransactionCommitFailedException> expFuture = mock(CheckedFuture.class);
+        when(this.rwTransaction.submit()).thenReturn(expFuture);
 
-        when(wTransaction.submit()).thenReturn(expFuture);
-
-        final CheckedFuture<Void, TransactionCommitFailedException> actualFuture = brokerFacade
-                .commitConfigurationDataDelete(instanceID);
+        // test
+        final CheckedFuture<Void, TransactionCommitFailedException> actualFuture = this.brokerFacade
+                .commitConfigurationDataDelete(this.instanceID);
 
+        // verify result and interactions
         assertSame("commitConfigurationDataDelete", expFuture, actualFuture);
 
-        final InOrder inOrder = inOrder(domDataBroker, wTransaction);
-        inOrder.verify(domDataBroker).newWriteOnlyTransaction();
-        inOrder.verify(wTransaction).delete(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class));
-        inOrder.verify(wTransaction).submit();
+        // check exists, delete, submit
+        final InOrder inOrder = inOrder(this.domDataBroker, this.rwTransaction);
+        inOrder.verify(this.rwTransaction).exists(LogicalDatastoreType.CONFIGURATION, this.instanceID);
+        inOrder.verify(this.rwTransaction).delete(LogicalDatastoreType.CONFIGURATION, this.instanceID);
+        inOrder.verify(this.rwTransaction).submit();
+    }
+
+    /**
+     * Negative test of delete operation when data to delete does not exist. Error 404 should be returned.
+     */
+    @Test
+    public void testCommitConfigurationDataDeleteNoData() throws Exception {
+        // assume that data to delete does not exist
+        prepareDataForDelete(false);
+
+        // try to delete and expect 404 error
+        try {
+            this.brokerFacade.commitConfigurationDataDelete(this.instanceID);
+            fail("Delete operation should fail due to missing data");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals(ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals(ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals(404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Prepare conditions to test delete operation. Data to delete exists or does not exist according to value of
+     * {@code assumeDataExists} parameter.
+     * @param assumeDataExists
+     */
+    private void prepareDataForDelete(final boolean assumeDataExists) {
+        when(this.rwTransaction.exists(LogicalDatastoreType.CONFIGURATION, this.instanceID))
+                .thenReturn(Futures.immediateCheckedFuture(new Boolean(assumeDataExists)));
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testRegisterToListenDataChanges() {
-        final ListenerAdapter listener = Notificator.createListener(instanceID, "stream");
+        final ListenerAdapter listener = Notificator.createListener(this.instanceID, "stream");
 
+        @SuppressWarnings("unchecked")
         final ListenerRegistration<DOMDataChangeListener> mockRegistration = mock(ListenerRegistration.class);
 
-        when(
-                domDataBroker.registerDataChangeListener(any(LogicalDatastoreType.class), eq(instanceID), eq(listener),
-                        eq(DataChangeScope.BASE))).thenReturn(mockRegistration);
+        when(this.domDataBroker.registerDataChangeListener(any(LogicalDatastoreType.class), eq(this.instanceID),
+                eq(listener), eq(DataChangeScope.BASE))).thenReturn(mockRegistration);
 
-        brokerFacade.registerToListenDataChanges(LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
+        this.brokerFacade.registerToListenDataChanges(
+                LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
 
-        verify(domDataBroker).registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, instanceID, listener,
-                DataChangeScope.BASE);
+        verify(this.domDataBroker).registerDataChangeListener(
+                LogicalDatastoreType.CONFIGURATION, this.instanceID, listener, DataChangeScope.BASE);
 
         assertEquals("isListening", true, listener.isListening());
 
-        brokerFacade.registerToListenDataChanges(LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
-        verifyNoMoreInteractions(domDataBroker);
+        this.brokerFacade.registerToListenDataChanges(
+                LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
+        verifyNoMoreInteractions(this.domDataBroker);
+    }
 
+    /**
+     * Create, register, close and remove notification listener.
+     */
+    @Test
+    public void testRegisterToListenNotificationChanges() {
+        // create test notification listener
+        final String identifier = "create-notification-stream/toaster:toastDone";
+        final SchemaPath path = SchemaPath.create(true,
+                QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toastDone"));
+        Notificator.createNotificationListener(Lists.newArrayList(path), identifier, "XML");
+        final NotificationListenerAdapter listener = Notificator.getNotificationListenerFor(identifier).get(0);
+
+        // mock registration
+        final ListenerRegistration<NotificationListenerAdapter> registration = mock(ListenerRegistration.class);
+        when(this.domNotification.registerNotificationListener(listener, listener.getSchemaPath()))
+                .thenReturn(registration);
+
+        // test to register listener for the first time
+        this.brokerFacade.registerToListenNotification(listener);
+        assertEquals("Registration was not successful", true, listener.isListening());
+
+        // try to register for the second time
+        this.brokerFacade.registerToListenNotification(listener);
+        assertEquals("Registration was not successful", true, listener.isListening());
+
+        // registrations should be invoked only once
+        verify(this.domNotification, times(1)).registerNotificationListener(listener, listener.getSchemaPath());
+
+        // close and remove test notification listener
+        listener.close();
+        Notificator.removeNotificationListenerIfNoSubscriberExists(listener);
     }
 }
index 26a8632e72f1cb1296c7c33e0dc947305343b408..b472f1781f52e14dc082961b05d92975753d496a 100644 (file)
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import com.google.common.base.Optional;
 import com.google.common.io.Resources;
 import com.google.common.util.concurrent.Futures;
@@ -29,6 +30,7 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
+import javax.ws.rs.core.Response.Status;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -44,6 +46,7 @@ import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.netconf.sal.restconf.impl.JSONRestconfServiceImpl;
+import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.yangtools.yang.common.OperationFailedException;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -119,9 +122,12 @@ public class JSONRestconfServiceImplTest {
     @SuppressWarnings("rawtypes")
     @Test
     public void testPut() throws Exception {
-        doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPut(
-                notNull(SchemaContext.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
-
+        final PutResult result = mock(PutResult.class);
+        when(brokerFacade.commitConfigurationDataPut(notNull(SchemaContext.class),
+                notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class))).thenReturn(result);
+        when(result.getFutureOfPutData())
+                .thenReturn(Futures.immediateCheckedFuture(null));
+        when(result.getStatus()).thenReturn(Status.OK);
         final String uriPath = "ietf-interfaces:interfaces/interface/eth0";
         final String payload = loadData("/parts/ietf-interfaces_interfaces.json");
 
@@ -149,10 +155,11 @@ public class JSONRestconfServiceImplTest {
     @Test
     public void testPutBehindMountPoint() throws Exception {
         final DOMMountPoint mockMountPoint = setupTestMountPoint();
-
-        doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPut(
-                notNull(DOMMountPoint.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
-
+        final PutResult result = mock(PutResult.class);
+        when(brokerFacade.commitMountPointDataPut(notNull(DOMMountPoint.class),
+                notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class))).thenReturn(result);
+        when(result.getFutureOfPutData()).thenReturn(Futures.immediateCheckedFuture(null));
+        when(result.getStatus()).thenReturn(Status.OK);
         final String uriPath = "ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1";
         final String payload = loadData("/full-versions/testCont1Data.json");
 
@@ -160,7 +167,7 @@ public class JSONRestconfServiceImplTest {
 
         final ArgumentCaptor<YangInstanceIdentifier> capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class);
         final ArgumentCaptor<NormalizedNode> capturedNode = ArgumentCaptor.forClass(NormalizedNode.class);
-        verify(brokerFacade).commitConfigurationDataPut(same(mockMountPoint), capturedPath.capture(),
+        verify(brokerFacade).commitMountPointDataPut(same(mockMountPoint), capturedPath.capture(),
                 capturedNode.capture());
 
         verifyPath(capturedPath.getValue(), TEST_CONT_QNAME, TEST_CONT1_QNAME);
@@ -172,21 +179,19 @@ public class JSONRestconfServiceImplTest {
         verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data");
     }
 
-    @Test(expected=TransactionCommitFailedException.class)
+    @Test(expected = OperationFailedException.class)
     public void testPutFailure() throws Throwable {
-        doReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock")))
-                .when(brokerFacade).commitConfigurationDataPut(notNull(SchemaContext.class),
-                        notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
+        final PutResult result = mock(PutResult.class);
+        when(result.getFutureOfPutData())
+                .thenReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock")));
+        when(result.getStatus()).thenReturn(Status.OK);
+        when(brokerFacade.commitConfigurationDataPut(notNull(SchemaContext.class),
+                notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class))).thenReturn(result);
 
         final String uriPath = "ietf-interfaces:interfaces/interface/eth0";
         final String payload = loadData("/parts/ietf-interfaces_interfaces.json");
 
-        try {
-            this.service.put(uriPath, payload);
-        } catch (final OperationFailedException e) {
-            assertNotNull(e.getCause());
-            throw e.getCause();
-        }
+        this.service.put(uriPath, payload);
     }
 
     @SuppressWarnings("rawtypes")
@@ -231,7 +236,6 @@ public class JSONRestconfServiceImplTest {
     @Test
     public void testPostBehindMountPoint() throws Exception {
         final DOMMountPoint mockMountPoint = setupTestMountPoint();
-
         doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataPost(
                 notNull(DOMMountPoint.class), notNull(YangInstanceIdentifier.class), notNull(NormalizedNode.class));
 
@@ -254,27 +258,22 @@ public class JSONRestconfServiceImplTest {
         verifyLeafNode(actualNode, TEST_LF12_QNAME, "lf12 data");
     }
 
-    @Test(expected=TransactionCommitFailedException.class)
+    @Test
     public void testPostFailure() throws Throwable {
-        doReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock")))
-                .when(brokerFacade).commitConfigurationDataPost(any(SchemaContext.class),
-                        any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+        doReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("mock"))).when(brokerFacade)
+                .commitConfigurationDataPost(any(SchemaContext.class), any(YangInstanceIdentifier.class),
+                        any(NormalizedNode.class));
 
         final String uriPath = null;
         final String payload = loadData("/parts/ietf-interfaces_interfaces_absolute_path.json");
 
-        try {
-            this.service.post(uriPath, payload);
-        } catch (final OperationFailedException e) {
-            assertNotNull(e.getCause());
-            throw e.getCause();
-        }
+        this.service.post(uriPath, payload);
     }
 
     @Test
     public void testDelete() throws Exception {
-        doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade).commitConfigurationDataDelete(
-                notNull(YangInstanceIdentifier.class));
+        doReturn(Futures.immediateCheckedFuture(null)).when(brokerFacade)
+                .commitConfigurationDataDelete(notNull(YangInstanceIdentifier.class));
 
         final String uriPath = "ietf-interfaces:interfaces/interface/eth0";
 
@@ -305,14 +304,10 @@ public class JSONRestconfServiceImplTest {
     }
 
     @Test
-    public void testGetWithNoData() throws Exception {
+    public void testGetWithNoData() throws OperationFailedException {
         doReturn(null).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class));
-
         final String uriPath = "ietf-interfaces:interfaces";
-
-        final Optional<String> optionalResp = this.service.get(uriPath, LogicalDatastoreType.CONFIGURATION);
-
-        assertEquals("Response present", false, optionalResp.isPresent());
+        this.service.get(uriPath, LogicalDatastoreType.CONFIGURATION);
     }
 
     @Test(expected=OperationFailedException.class)
index 53b37fb29cd83dec2d5d2e72c1c724de6f603a8a..b5435e149a1c25ae48646a4662f039a80168646f 100644 (file)
@@ -13,7 +13,7 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 import java.io.FileNotFoundException;
 import java.io.UnsupportedEncodingException;
 import java.util.Set;
@@ -72,12 +72,11 @@ public class RestDeleteOperationTest extends JerseyTest {
         return resourceConfig;
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void deleteConfigStatusCodes() throws UnsupportedEncodingException {
         final String uri = "/config/test-interface:interfaces";
         when(brokerFacade.commitConfigurationDataDelete(any(YangInstanceIdentifier.class))).thenReturn(
-                mock(CheckedFuture.class));
+                Futures.immediateCheckedFuture(null));
         Response response = target(uri).request(MediaType.APPLICATION_XML).delete();
         assertEquals(200, response.getStatus());
 
index bb879722cba0b14ef9405b1a3911879cd84919b4..972ab2d428fb521c764ee211e7fec844ceafa9ad 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.controller.sal.restconf.impl.test;
 
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
+import javax.ws.rs.core.Response.Status;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -22,6 +23,7 @@ import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -53,17 +55,17 @@ public class RestPutConfigTest {
 
     @Before
     public void init() {
-        restconfService = RestconfImpl.getInstance();
-        controllerCx = ControllerContext.getInstance();
-        schemaCx = TestRestconfUtils.loadSchemaContext("/test-config-data/yang1/", null);
-        controllerCx.setSchemas(schemaCx);
-        restconfService.setControllerContext(controllerCx);
+        this.restconfService = RestconfImpl.getInstance();
+        this.controllerCx = ControllerContext.getInstance();
+        this.schemaCx = TestRestconfUtils.loadSchemaContext("/test-config-data/yang1/", null);
+        this.controllerCx.setSchemas(this.schemaCx);
+        this.restconfService.setControllerContext(this.controllerCx);
     }
 
     @Test
     public void testPutConfigData() {
         final String identifier = "test-interface:interfaces/interface/key";
-        final InstanceIdentifierContext<?> iiCx = controllerCx.toInstanceIdentifier(identifier);
+        final InstanceIdentifierContext<?> iiCx = this.controllerCx.toInstanceIdentifier(identifier);
         final MapEntryNode data = Mockito.mock(MapEntryNode.class);
         final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "interface");
         final QName qNameKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "name");
@@ -74,13 +76,13 @@ public class RestPutConfigTest {
 
         mockingBrokerPut(iiCx.getInstanceIdentifier(), data);
 
-        restconfService.updateConfigurationData(identifier, payload);
+        this.restconfService.updateConfigurationData(identifier, payload);
     }
 
     @Test
     public void testPutConfigDataCheckOnlyLastElement() {
         final String identifier = "test-interface:interfaces/interface/key/sub-interface/subkey";
-        final InstanceIdentifierContext<?> iiCx = controllerCx.toInstanceIdentifier(identifier);
+        final InstanceIdentifierContext<?> iiCx = this.controllerCx.toInstanceIdentifier(identifier);
         final MapEntryNode data = Mockito.mock(MapEntryNode.class);
         final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "sub-interface");
         final QName qNameSubKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "sub-name");
@@ -91,19 +93,19 @@ public class RestPutConfigTest {
 
         mockingBrokerPut(iiCx.getInstanceIdentifier(), data);
 
-        restconfService.updateConfigurationData(identifier, payload);
+        this.restconfService.updateConfigurationData(identifier, payload);
     }
 
     @Test(expected=RestconfDocumentedException.class)
     public void testPutConfigDataMissingUriKey() {
         final String identifier = "test-interface:interfaces/interface";
-        controllerCx.toInstanceIdentifier(identifier);
+        this.controllerCx.toInstanceIdentifier(identifier);
     }
 
     @Test(expected=RestconfDocumentedException.class)
     public void testPutConfigDataDiferentKey() {
         final String identifier = "test-interface:interfaces/interface/key";
-        final InstanceIdentifierContext<?> iiCx = controllerCx.toInstanceIdentifier(identifier);
+        final InstanceIdentifierContext<?> iiCx = this.controllerCx.toInstanceIdentifier(identifier);
         final MapEntryNode data = Mockito.mock(MapEntryNode.class);
         final QName qName = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "interface");
         final QName qNameKey = QName.create("urn:ietf:params:xml:ns:yang:test-interface", "2014-07-01", "name");
@@ -114,12 +116,16 @@ public class RestPutConfigTest {
 
         mockingBrokerPut(iiCx.getInstanceIdentifier(), data);
 
-        restconfService.updateConfigurationData(identifier, payload);
+        this.restconfService.updateConfigurationData(identifier, payload);
     }
 
     private void mockingBrokerPut(final YangInstanceIdentifier yii, final NormalizedNode<?, ?> data) {
+        final PutResult result = Mockito.mock(PutResult.class);
         final CheckedFuture<Void, TransactionCommitFailedException> checkedFuture = Futures.immediateCheckedFuture(null);
-        Mockito.when(brokerFacade.commitConfigurationDataPut(schemaCx, yii, data)).thenReturn(checkedFuture);
-        restconfService.setBroker(brokerFacade);
+        Mockito.when(this.brokerFacade.commitConfigurationDataPut(this.schemaCx, yii, data))
+                .thenReturn(result);
+        Mockito.when(result.getFutureOfPutData()).thenReturn(checkedFuture);
+        Mockito.when(result.getStatus()).thenReturn(Status.OK);
+        this.restconfService.setBroker(this.brokerFacade);
     }
 }
index 80cf7a34e7df78f42d31f18d7c2e40a20313a168..160df40e5d7d9115980f4f9425857741a8c81a3c 100644 (file)
@@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import com.google.common.base.Optional;
 import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -24,10 +25,10 @@ import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
@@ -40,6 +41,7 @@ import org.opendaylight.netconf.sal.rest.impl.RestconfDocumentedExceptionMapper;
 import org.opendaylight.netconf.sal.rest.impl.XmlNormalizedNodeBodyReader;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -47,7 +49,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
 
-@Ignore
 public class RestPutOperationTest extends JerseyTest {
 
     private static String xmlData;
@@ -102,7 +103,7 @@ public class RestPutOperationTest extends JerseyTest {
     public void putConfigStatusCodes() throws UnsupportedEncodingException {
         final String uri = "/config/ietf-interfaces:interfaces/interface/eth0";
         mockCommitConfigurationDataPutMethod(true);
-        assertEquals(200, put(uri, MediaType.APPLICATION_XML, xmlData));
+        assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData));
 
         mockCommitConfigurationDataPutMethod(false);
         assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData));
@@ -120,16 +121,15 @@ public class RestPutOperationTest extends JerseyTest {
     }
 
     @Test
-    @Ignore // jenkins has problem with JerseyTest - we expecting problems with singletons ControllerContext as schemaContext holder
     public void testRpcResultCommitedToStatusCodesWithMountPoint() throws UnsupportedEncodingException,
             FileNotFoundException, URISyntaxException {
-
-        @SuppressWarnings("unchecked")
-        final CheckedFuture<Void, TransactionCommitFailedException> dummyFuture = mock(CheckedFuture.class);
-
+        final CheckedFuture<Void, TransactionCommitFailedException> dummyFuture = Futures.immediateCheckedFuture(null);
+        final PutResult result = mock(PutResult.class);
         when(
-                brokerFacade.commitConfigurationDataPut(any(DOMMountPoint.class), any(YangInstanceIdentifier.class),
-                        any(NormalizedNode.class))).thenReturn(dummyFuture);
+                brokerFacade.commitMountPointDataPut(any(DOMMountPoint.class), any(YangInstanceIdentifier.class),
+                        any(NormalizedNode.class))).thenReturn(result);
+        when(result.getFutureOfPutData()).thenReturn(dummyFuture);
+        when(result.getStatus()).thenReturn(Status.OK);
 
         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
@@ -147,11 +147,12 @@ public class RestPutOperationTest extends JerseyTest {
 
     @Test
     public void putDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException {
-        @SuppressWarnings("unchecked")
-        final CheckedFuture<Void, TransactionCommitFailedException> dummyFuture = mock(CheckedFuture.class);
-        when(
-                brokerFacade.commitConfigurationDataPut(any(DOMMountPoint.class), any(YangInstanceIdentifier.class),
-                        any(NormalizedNode.class))).thenReturn(dummyFuture);
+        final CheckedFuture<Void, TransactionCommitFailedException> dummyFuture = Futures.immediateCheckedFuture(null);
+        final PutResult result = mock(PutResult.class);
+        doReturn(result).when(brokerFacade).commitMountPointDataPut(any(DOMMountPoint.class),
+                any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+        when(result.getFutureOfPutData()).thenReturn(dummyFuture);
+        when(result.getStatus()).thenReturn(Status.OK);
 
         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
@@ -171,26 +172,25 @@ public class RestPutOperationTest extends JerseyTest {
 
         doThrow(OptimisticLockFailedException.class).
             when(brokerFacade).commitConfigurationDataPut(
-                any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+                        any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
 
         assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData));
 
-        doThrow(OptimisticLockFailedException.class).doReturn(mock(CheckedFuture.class)).
-            when(brokerFacade).commitConfigurationDataPut(
-                any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+        doThrow(OptimisticLockFailedException.class).doReturn(mock(PutResult.class)).when(brokerFacade)
+                .commitConfigurationDataPut(any(SchemaContext.class), any(YangInstanceIdentifier.class),
+                        any(NormalizedNode.class));
 
-        assertEquals(200, put(uri, MediaType.APPLICATION_XML, xmlData));
+        assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData));
     }
 
     @Test
-    @Ignore // jenkins has problem with JerseyTest - we expecting problems with singletons ControllerContext as schemaContext holder
     public void putWithTransactionCommitFailedException() throws UnsupportedEncodingException {
 
         final String uri = "/config/ietf-interfaces:interfaces/interface/eth0";
 
         doThrow(TransactionCommitFailedException.class).
             when(brokerFacade).commitConfigurationDataPut(
-                (SchemaContext)null, any(YangInstanceIdentifier.class), any(NormalizedNode.class));
+                        any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
 
         assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData));
     }
@@ -200,8 +200,9 @@ public class RestPutOperationTest extends JerseyTest {
     }
 
     private void mockCommitConfigurationDataPutMethod(final boolean noErrors) {
+        final PutResult putResMock = mock(PutResult.class);
         if (noErrors) {
-            doReturn(mock(CheckedFuture.class)).when(brokerFacade).commitConfigurationDataPut(
+            doReturn(putResMock).when(brokerFacade).commitConfigurationDataPut(
                     any(SchemaContext.class), any(YangInstanceIdentifier.class), any(NormalizedNode.class));
         } else {
             doThrow(RestconfDocumentedException.class).when(brokerFacade).commitConfigurationDataPut(
index 1c57effe4590a7bd2760907a100d255b687faf82..7c4b350c6cf622a3259dfe43b0d5c8d051991591 100644 (file)
@@ -13,20 +13,26 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+
 import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Futures;
 import java.io.FileNotFoundException;
+import java.net.URI;
 import java.text.ParseException;
 import java.util.Set;
 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.BeforeClass;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
@@ -35,10 +41,16 @@ import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 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.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
@@ -60,9 +72,8 @@ public class RestconfImplTest {
         final Set<Module> allModules = schemaContext.getModules();
         assertNotNull(allModules);
 
-        controllerContext = spy(ControllerContext.getInstance());
+        controllerContext = ControllerContext.getInstance();
         controllerContext.setSchemas(schemaContext);
-
     }
 
     @Before
@@ -105,4 +116,73 @@ public class RestconfImplTest {
         this.restconfImpl.invokeRpc("ietf-netconf", ctx, uriInfo);
         verify(rpcService, times(2)).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
     }
+
+    /**
+     * Create notification stream for toaster module
+     */
+    @Test
+    public void createNotificationStreamTest() {
+        final NormalizedNodeContext payload = mock(NormalizedNodeContext.class);
+        final InstanceIdentifierContext iiCtx = mock(InstanceIdentifierContext.class);
+        doReturn(iiCtx).when(payload).getInstanceIdentifierContext();
+
+        final SchemaNode schemaNode = mock(SchemaNode.class,
+                Mockito.withSettings().extraInterfaces(RpcDefinition.class));
+        doReturn(schemaNode).when(iiCtx).getSchemaNode();
+        doReturn(mock(SchemaPath.class)).when(schemaNode).getPath();
+
+        doReturn(QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote",
+                "2014-01-14", "create-notification-stream")).when(schemaNode).getQName();
+        doReturn(null).when(iiCtx).getMountPoint();
+
+        final Set<DataContainerChild<?, ?>> children = Sets.newHashSet();
+        final DataContainerChild<?, ?> child = mock(DataContainerChild.class,
+                Mockito.withSettings().extraInterfaces(LeafSetNode.class));
+
+        final LeafSetEntryNode entryNode = mock(LeafSetEntryNode.class);
+        when(entryNode.getValue()).thenReturn("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)toastDone");
+        when(((LeafSetNode) child).getValue()).thenReturn(Sets.newHashSet(entryNode));
+        children.add(child);
+
+        final NormalizedNode<?, ?> normalizedNode = mock(NormalizedNode.class,
+                Mockito.withSettings().extraInterfaces(ContainerNode.class));
+        doReturn(normalizedNode).when(payload).getData();
+        doReturn(children).when(normalizedNode).getValue();
+
+        // register notification
+        final NormalizedNodeContext context = this.restconfImpl
+                .invokeRpc("sal-remote:create-notification-stream", payload, null);
+        assertNotNull(context);
+    }
+
+    /**
+     * Subscribe for notification stream of toaster module
+     */
+    @Test
+    public void subscribeToNotificationStreamTest() throws Exception {
+        final String identifier = "create-notification-stream/toaster:toastDone";
+
+        // register test notification stream
+        final SchemaPath path = SchemaPath.create(
+                true, QName.create("http://netconfcentral.org/ns/toaster", "2009-11-20", "toastDone"));
+        Notificator.createNotificationListener(Lists.newArrayList(path), identifier, "XML");
+
+        final UriInfo uriInfo = mock(UriInfo.class);
+        final UriBuilder uriBuilder = mock(UriBuilder.class);
+        when(uriBuilder.port(8181)).thenReturn(uriBuilder);
+        when(uriBuilder.replacePath(identifier)).thenReturn(uriBuilder);
+        when(uriBuilder.build()).thenReturn(new URI(""));
+        when(uriBuilder.scheme("ws")).thenReturn(uriBuilder);
+        when(uriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder);
+
+        final BrokerFacade brokerFacade = mock(BrokerFacade.class);
+        this.restconfImpl.setBroker(brokerFacade);
+
+        // subscribe to stream and verify response
+        final Response response = this.restconfImpl.subscribeToStream(identifier, uriInfo);
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+
+        // remove test notification stream
+        Notificator.removeAllListeners();
+    }
 }
index 8907e5fcc35c655b25110be57945c15506ef4ec0..5e0e266cacba444c85beb7c3c5fb02d874872614 100644 (file)
@@ -9,6 +9,7 @@
 package org.opendaylight.restconf.parser;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -22,8 +23,8 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 public class IdentifierCodecTest {
 
     private static final String URI_WITH_LIST_AND_LEAF =
-            "/list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result";
-    private static final String URI_WITH_LEAF_LIST = "/list-test:top/Y=x%3Ay";
+            "list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result";
+    private static final String URI_WITH_LEAF_LIST = "list-test:top/Y=x%3Ay";
 
     private SchemaContext schemaContext;
 
@@ -74,13 +75,13 @@ public class IdentifierCodecTest {
     }
 
     /**
-     * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code>. Single slash is
+     * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code>. Empty <code>String</code> is
      * expected to be returned.
      */
     @Test
     public void codecSerializeEmptyTest () {
         final String serialized = IdentifierCodec.serialize(YangInstanceIdentifier.EMPTY, this.schemaContext);
-        assertEquals("Failed codec serialization test", "/", serialized);
+        assertTrue("Failed codec serialization test", serialized.isEmpty());
     }
 
     /**
index 7ba1bace71f0705c2e6f81b873870fba61304c97..43b9b218a9d15565320eb60702ff22357e9d6bae 100644 (file)
@@ -27,6 +27,7 @@ import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 /**
@@ -52,7 +53,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeContainerTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:contA");
+                .create(schemaContext, "deserializer-test:contA");
 
         assertEquals("Result does not contains expected number of path arguments", 1, Iterables.size(result));
         assertEquals("Not expected path argument",
@@ -67,7 +68,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeContainerWithLeafTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:contA/leaf-A");
+                .create(schemaContext, "deserializer-test:contA/leaf-A");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -87,7 +88,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeContainerWithListWithLeafListTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:contA/list-A=100/leaf-list-AA=instance");
+                .create(schemaContext, "deserializer-test:contA/list-A=100/leaf-list-AA=instance");
 
         assertEquals("Result does not contains expected number of path arguments", 5, Iterables.size(result));
 
@@ -125,7 +126,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeListWithNoKeysTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:list-no-key");
+                .create(schemaContext, "deserializer-test:list-no-key");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -147,7 +148,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeListWithOneKeyTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:list-one-key=value");
+                .create(schemaContext, "deserializer-test:list-one-key=value");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -175,7 +176,7 @@ public class YangInstanceIdentifierDeserializerTest {
         values.put(QName.create(list, "enabled"), false);
 
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:list-multiple-keys=value,100,false");
+                .create(schemaContext, "deserializer-test:list-multiple-keys=value,100,false");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -196,7 +197,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeLeafListTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:leaf-list-0=true");
+                .create(schemaContext, "deserializer-test:leaf-list-0=true");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -211,6 +212,15 @@ public class YangInstanceIdentifierDeserializerTest {
                 iterator.next().toString());
     }
 
+    /**
+     * Test when empty <code>String</code> is supplied as an input. Test is expected to return empty result.
+     */
+    @Test
+    public void deserializeEmptyDataTest() {
+        final Iterable<PathArgument> result = YangInstanceIdentifierDeserializer.create(schemaContext, "");
+        assertTrue("Empty result expected", Iterables.isEmpty(result));
+    }
+
     /**
      * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
      * <code>NullPointerException</code>.
@@ -218,7 +228,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeNullSchemaContextNegativeTest() {
         thrown.expect(NullPointerException.class);
-        YangInstanceIdentifierDeserializer.create(null, "/deserializer-test:contA");
+        YangInstanceIdentifierDeserializer.create(null, "deserializer-test:contA");
     }
 
     /**
@@ -231,16 +241,6 @@ public class YangInstanceIdentifierDeserializerTest {
         YangInstanceIdentifierDeserializer.create(schemaContext, null);
     }
 
-    /**
-     * Negative test when empty <code>String</code> is supplied as an input. Test is expected to fail with
-     * <code>IllegalArgumentException</code>.
-     */
-    @Test
-    public void deserializeEmptyDataNegativeTest() {
-        thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "");
-    }
-
     /**
      * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
      * <code>IllegalArgumentException</code>.
@@ -248,28 +248,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:cont*leaf-A");
-    }
-
-    /**
-     * Negative test of validating identifier when identifier does not start with slash.
-     * <code>IllegalArgumentException</code> is expected.
-     */
-    @Test
-    public void deserializeNoBeginningSlashNegativeTest() {
-        thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA");
-    }
-
-    /**
-     * Positive test of validating identifier when identifier contains slash only. Deserialization should return
-     * empty result.
-     */
-    @Test
-    public void validArgOnlySlashTest() {
-        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/");
-        assertTrue("Result does not contains expected number of path arguments", Iterables.isEmpty(result));
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:cont*leaf-A");
     }
 
     /**
@@ -279,7 +258,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA/");
     }
 
     /**
@@ -289,17 +268,17 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void validArgIdentifierListEndsWithSlashLNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key=value/");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:list-one-key=value/");
     }
 
     /**
-     * Negative test of creating <code>QName</code> when identifier is empty (example: '//'). Test is expected to fail
+     * Negative test of creating <code>QName</code> when identifier is empty (example: '/'). Test is expected to fail
      * with <code>IllegalArgumentException</code>.
      */
     @Test
     public void prepareQnameEmptyIdentifierNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "//");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/");
     }
 
     /**
@@ -309,7 +288,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameTwoSlashesNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA//leaf-A");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA//leaf-A");
     }
 
     /**
@@ -319,7 +298,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameBuildPathNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test*contA");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test*contA");
     }
 
     /**
@@ -329,7 +308,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameNotExistingPrefixNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/not-existing:contA");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "not-existing:contA");
     }
 
     /**
@@ -339,7 +318,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:*not-parsable-identifier");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:*not-parsable-identifier");
     }
 
     /**
@@ -349,7 +328,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameErrorParsingNegativeTest() {
         thrown.expect(StringIndexOutOfBoundsException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:");
     }
 
     /**
@@ -360,7 +339,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameNotValidContainerNameNegativeTest() {
         try {
-            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/leafB");
+            YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA/leafB");
             fail("Test should fail due to unknown child node in container");
         } catch (final RestconfDocumentedException e) {
             assertEquals("Not expected error type",
@@ -380,7 +359,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareQnameNotValidListNameNegativeTest() {
         try {
-            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-no-key/disabled=false");
+            YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:list-no-key/disabled=false");
             fail("Test should fail due to unknown child node in list");
         } catch (final RestconfDocumentedException e) {
             assertEquals("Not expected error type",
@@ -399,7 +378,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void prepareIdentifierNotKeyedEntryNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
-        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key");
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:list-one-key");
     }
 
     /**
@@ -410,7 +389,7 @@ public class YangInstanceIdentifierDeserializerTest {
     public void deserializeKeysEndsWithComaNegativeTest() {
         thrown.expect(IllegalArgumentException.class);
         YangInstanceIdentifierDeserializer.create( schemaContext,
-                "/deserializer-test:list-multiple-keys=value,100,false,");
+                "deserializer-test:list-multiple-keys=value,100,false,");
     }
 
     /**
@@ -426,7 +405,7 @@ public class YangInstanceIdentifierDeserializerTest {
         values.put(QName.create(list, "enabled"), "");
 
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
-                schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo,,/string-value");
+                schemaContext, "deserializer-test:list-multiple-keys=%3Afoo,,/string-value");
 
         assertEquals("Result does not contains expected number of path arguments", 3, Iterables.size(result));
 
@@ -456,7 +435,7 @@ public class YangInstanceIdentifierDeserializerTest {
     public void notAllListKeysEncodedNegativeTest() {
         try {
             YangInstanceIdentifierDeserializer.create(
-                    schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo/string-value");
+                    schemaContext, "deserializer-test:list-multiple-keys=%3Afoo/string-value");
             fail("Test should fail due to missing list key values");
         } catch (final RestconfDocumentedException e) {
             assertEquals("Not expected error type",
@@ -468,21 +447,13 @@ public class YangInstanceIdentifierDeserializerTest {
         }
     }
 
-    /**
-     * Negative test of preparing node with predicates when it is not possible to get <code>DataSchemaNode</code>.
-     * Test is expected to fail with <code>NullPointerException</code>.
-     */
-    @Ignore
-    @Test
-    public void prepareNodeWithPredicatesNegativeTest() {}
-
     /**
      * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
      * value should be complete also with not percent-encoded parts.
      */
     @Test
     public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
-        final String URI = "/deserializer-test:list-multiple-keys=%3Afoo,bar%3A,foo%3Abar";
+        final String URI = "deserializer-test:list-multiple-keys=%3Afoo,bar%3A,foo%3Abar";
         final YangInstanceIdentifier result = YangInstanceIdentifier.create(
                 YangInstanceIdentifierDeserializer.create(schemaContext, URI));
 
@@ -506,7 +477,7 @@ public class YangInstanceIdentifierDeserializerTest {
         values.put(QName.create(list, "enabled"), "");
 
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
-                .create(schemaContext, "/deserializer-test:list-multiple-keys=,,");
+                .create(schemaContext, "deserializer-test:list-multiple-keys=,,");
 
         assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
 
@@ -528,7 +499,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void leafListMissingKeyNegativeTest() {
         try {
-            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:leaf-list-0=");
+            YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:leaf-list-0=");
             fail("Test should fail due to missing instance value");
         } catch (final RestconfDocumentedException e) {
             assertEquals("Not expected error type",
@@ -546,7 +517,7 @@ public class YangInstanceIdentifierDeserializerTest {
     @Test
     public void deserializePartInOtherModuleTest() {
         final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
-                schemaContext, "/deserializer-test-included:augmented-list=100/augmented-leaf");
+                schemaContext, "deserializer-test-included:augmented-list=100/augmented-leaf");
 
         assertEquals("Result does not contains expected number of path arguments", 4, Iterables.size(result));
 
index 965ce3499528cdbfb90ea7904118d77dba549609..b05f43f4f62378a78093b52ea86b0d75c10bbe5f 100644 (file)
@@ -54,7 +54,7 @@ public class YangInstanceIdentifierSerializerTest {
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
         assertEquals("Serialization not successful",
-                "/serializer-test:contA", result);
+                "serializer-test:contA", result);
     }
 
     /**
@@ -69,7 +69,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:contA/leaf-A", result);
+        assertEquals("Serialization not successful", "serializer-test:contA/leaf-A", result);
     }
 
     /**
@@ -92,7 +92,7 @@ public class YangInstanceIdentifierSerializerTest {
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
         assertEquals("Serialization not successful",
-                "/serializer-test:contA/list-A=100/leaf-list-AA=instance",
+                "serializer-test:contA/list-A=100/leaf-list-AA=instance",
                 result);
     }
 
@@ -109,7 +109,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:list-no-key", result);
+        assertEquals("Serialization not successful", "serializer-test:list-no-key", result);
     }
 
     /**
@@ -126,7 +126,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:list-one-key=value", result);
+        assertEquals("Serialization not successful", "serializer-test:list-one-key=value", result);
     }
 
     /**
@@ -146,7 +146,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .node(list).nodeWithKey(list, values).build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:list-multiple-keys=value-1,2,true", result);
+        assertEquals("Serialization not successful", "serializer-test:list-multiple-keys=value-1,2,true", result);
     }
 
     /**
@@ -161,7 +161,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:leaf-0", result);
+        assertEquals("Serialization not successful", "serializer-test:leaf-0", result);
     }
 
     /**
@@ -177,7 +177,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:leaf-list-0=instance", result);
+        assertEquals("Serialization not successful", "serializer-test:leaf-list-0=instance", result);
     }
 
     /**
@@ -205,12 +205,12 @@ public class YangInstanceIdentifierSerializerTest {
     /**
      * Test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when supplied
      * <code>YangInstanceIdentifier</code> is <code>YangInstanceIdentifier.EMPTY</code>.
-     * Single slash is expected as a return value.
+     * Empty <code>String</code> is expected as a return value.
      */
     @Test
     public void serializeEmptyDataTest() {
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, YangInstanceIdentifier.EMPTY);
-        assertEquals("Empty identifier is expected", "/", result);
+        assertTrue("Empty identifier is expected", result.isEmpty());
     }
 
     /**
@@ -263,7 +263,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + encoded, result);
+        assertEquals("Serialization not successful", "serializer-test:list-one-key=" + encoded, result);
     }
 
     /**
@@ -280,7 +280,7 @@ public class YangInstanceIdentifierSerializerTest {
                 .build();
 
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
-        assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + value, result);
+        assertEquals("Serialization not successful", "serializer-test:list-one-key=" + value, result);
     }
 
     /**
@@ -303,7 +303,7 @@ public class YangInstanceIdentifierSerializerTest {
         final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
 
         assertEquals("Serialization not successful",
-                "/serializer-test-included:augmented-list=100/serializer-test:augmented-leaf", result);
+                "serializer-test-included:augmented-list=100/serializer-test:augmented-leaf", result);
     }
 
     /**
index 34310909149ccfcee89e61294c9fab60e6ca47b3..4f221b0ae1663bab8c1fd673c515ec1bb80c631a 100644 (file)
@@ -48,8 +48,8 @@ import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceIm
 import org.opendaylight.yangtools.yang.parser.util.NamedFileInputStream;
 
 class RestconfModulesServiceTestUtils {
-    static final String MOUNT_POINT = "/mount-point-1:cont/" + RestconfConstants.MOUNT + "/";
-    static final String NOT_REGISTERED_MOUNT_POINT = "/mount-point-1:listA/" + RestconfConstants.MOUNT + "/";
+    static final String MOUNT_POINT = "mount-point-1:cont/" + RestconfConstants.MOUNT + "/";
+    static final String NOT_REGISTERED_MOUNT_POINT = "mount-point-1:listA/" + RestconfConstants.MOUNT + "/";
 
     static final String TEST_MODULE = "module1/2014-01-01";
     static final String NOT_EXISTING_MODULE = "not-existing/2016-01-01";
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/DeleteDataTransactionUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/DeleteDataTransactionUtilTest.java
new file mode 100644 (file)
index 0000000..f867ab1
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.Futures;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import org.junit.Before;
+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.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+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.data.api.YangInstanceIdentifier;
+
+public class DeleteDataTransactionUtilTest {
+    @Mock DOMDataReadWriteTransaction transaction;
+    @Mock InstanceIdentifierContext<?> context;
+
+    @Before
+    public void init() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        Mockito.when(this.transaction.submit()).thenReturn(Futures.immediateCheckedFuture(null));
+        Mockito.when(context.getInstanceIdentifier()).thenReturn(YangInstanceIdentifier.EMPTY);
+    }
+
+    /**
+     * Test of successful DELETE operation.
+     */
+    @Test
+    public void deleteData() throws Exception {
+        // assert that data to delete exists
+        Mockito.when(this.transaction.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.EMPTY))
+                .thenReturn(Futures.immediateCheckedFuture(Boolean.TRUE));
+
+        // test
+        final Response response = DeleteDataTransactionUtil.deleteData(
+                new TransactionVarsWrapper(this.context, null, this.transaction));
+
+        // assert success
+        assertEquals("Not expected response received", Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    /**
+     * Negative test for DELETE operation when data to delete does not exist. Error 404 is expected.
+     */
+    @Test
+    public void deleteDataNegativeTest() throws Exception {
+        // assert that data to delete does NOT exist
+        Mockito.when(this.transaction.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.EMPTY))
+                .thenReturn(Futures.immediateCheckedFuture(Boolean.FALSE));
+
+        // test and assert error
+        try {
+            DeleteDataTransactionUtil.deleteData(new TransactionVarsWrapper(this.context, null, this.transaction));
+            fail("Delete operation should fail due to missing data");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals(ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals(ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals(404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+}
\ No newline at end of file
index 7793075db8dcb572fb9c6c69a61cfbc20cdea2f7..a5380a3004a55b27d54d132efbe12807e348ed00 100644 (file)
@@ -45,18 +45,18 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 public class ParserIdentifierTest {
     // mount point identifier + expected result
     private static final String MOUNT_POINT_IDENT =
-            "/mount-point:mount-container/point-number" + "/" + RestconfConstants.MOUNT;
+            "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;
+            "mount-point:point-number" + "/" + RestconfConstants.MOUNT;
 
     // test identifier + expected result
     private static final String TEST_IDENT =
-            "/parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
+            "parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
 
     private static final String TEST_IDENT_RESULT =
             "/(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/"
@@ -64,7 +64,7 @@ public class ParserIdentifierTest {
 
     // test identifier with nodes defined in other modules using augmentation + expected result
     private static final String TEST_IDENT_OTHERS =
-            "/parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
+            "parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
 
     private static final String TEST_IDENT_OTHERS_RESULT =
             "/(parser:identifier:included?revision=2016-06-02)list-1/list-1"
@@ -75,7 +75,7 @@ public class ParserIdentifierTest {
 
     // invalid test identifier
     private static final String INVALID_TEST_IDENT =
-            "/parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
+            "parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
 
     // schema context with test modules
     private SchemaContext schemaContext;
@@ -83,7 +83,6 @@ public class ParserIdentifierTest {
     private static final String TEST_MODULE_NAME = "test-module";
     private static final String TEST_MODULE_REVISION = "2016-06-02";
     private static final String TEST_MODULE_NAMESPACE = "test:module";
-    private static final String MOUNT_POINT_IDENT_WITHOUT_SLASH = MOUNT_POINT_IDENT.replaceFirst("/", "");
 
     // mount point and mount point service
     private DOMMountPoint mountPoint;
@@ -163,13 +162,14 @@ public class ParserIdentifierTest {
     }
 
     /**
-     * Negative test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>. Test
-     * fails expecting <code>NullPointerException</code>.
+     * Test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>.
+     * <code>{@link YangInstanceIdentifier#EMPTY}</code> should be returned.
      */
     @Test
-    public void toInstanceIdentifierNullIdentifierNegativeTest() {
-        thrown.expect(NullPointerException.class);
-        ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+    public void toInstanceIdentifierNullIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+        assertEquals("Returned not expected identifier",
+                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
     }
 
     /**
@@ -183,47 +183,27 @@ public class ParserIdentifierTest {
     }
 
     /**
-     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     * Api path can be empty. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
      */
     @Test
-    public void toInstanceIdentifierSlashIdentifierTest() {
-        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("/", schemaContext);
+    public void toInstanceIdentifierEmptyIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("", schemaContext);
         assertEquals("Returned not expected identifier",
                 YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
     }
 
     /**
-     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     * Api path can be empty. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
      * Test when identifier contains {@link RestconfConstants#MOUNT}.
      */
     @Test
-    public void toInstanceIdentifierSlashIdentifierMountPointTest() {
+    public void toInstanceIdentifierEmptyIdentifierMountPointTest() {
         final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
-                "/" + "/" + RestconfConstants.MOUNT, schemaContext);
+                "" + "/" + RestconfConstants.MOUNT, schemaContext);
         assertEquals("Returned not expected identifier",
                 YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
     }
 
-    /**
-     * Negative test of creating <code>InstanceIdentifierContext</code> with empty identifier.
-     * <code>IllegalArgumentException</code> is expected.
-     */
-    @Test
-    public void toInstanceIdentifierEmptyIdentifierNegativeTest() {
-        thrown.expect(IllegalArgumentException.class);
-        ParserIdentifier.toInstanceIdentifier("", schemaContext);
-    }
-
-    /**
-     * Negative test of creating <code>InstanceIdentifierContext</code> from identifier containing
-     * {@link RestconfConstants#MOUNT} when identifier part is empty. <code>IllegalArgumentException</code> is expected.
-     */
-    @Test
-    public void toInstanceIdentifierMountPointEmptyIdentifierNegativeTest() {
-        thrown.expect(IllegalArgumentException.class);
-        ParserIdentifier.toInstanceIdentifier("/" + RestconfConstants.MOUNT, schemaContext);
-    }
-
     /**
      * Negative test with invalid test identifier. Test should fail with <code>IllegalArgumentException</code>.
      */
@@ -493,7 +473,7 @@ public class ParserIdentifierTest {
     public void toSchemaExportContextFromIdentifierMountPointTest() {
         final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
                 schemaContext,
-                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+                MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
                 mountPointService);
 
         final Module module = exportContext.getModule();
@@ -515,7 +495,7 @@ public class ParserIdentifierTest {
     public void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
         final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
                 schemaContext,
-                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + "not-existing-module" + "/" + "2016-01-01",
+                MOUNT_POINT_IDENT + "/" + "not-existing-module" + "/" + "2016-01-01",
                 mountPointService);
 
         assertNotNull("Export context should be parsed", exportContext);
@@ -532,7 +512,7 @@ public class ParserIdentifierTest {
         try {
             ParserIdentifier.toSchemaExportContextFromIdentifier(
                     schemaContext,
-                    MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME,
+                    MOUNT_POINT_IDENT + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME,
                     mountPointService);
 
             fail("Test should fail due to invalid identifier supplied");
@@ -546,19 +526,6 @@ public class ParserIdentifierTest {
         }
     }
 
-    /**
-     * Negative test of getting <code>SchemaExportContext</code> with identifier beginning with slash defining module
-     * behind mount point. Test is expected to fail with <code>IllegalArgumentException</code>.
-     */
-    @Test
-    public void toSchemaExportContextFromIdentifierMountPointBeginsWithSlashNegativeTest() {
-        thrown.expect(IllegalArgumentException.class);
-        ParserIdentifier.toSchemaExportContextFromIdentifier(
-                schemaContext,
-                MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
-                mountPointService);
-    }
-
     /**
      * Negative test of getting <code>SchemaExportContext</code> when supplied identifier is null.
      * <code>NullPointerException</code> is expected. <code>DOMMountPointService</code> is not used.
@@ -589,7 +556,7 @@ public class ParserIdentifierTest {
         thrown.expect(NullPointerException.class);
         ParserIdentifier.toSchemaExportContextFromIdentifier(
                 null,
-                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                MOUNT_POINT_IDENT
                 + "/"
                 + TEST_MODULE_NAME
                 + "/"
@@ -607,7 +574,7 @@ public class ParserIdentifierTest {
         thrown.expect(NullPointerException.class);
         ParserIdentifier.toSchemaExportContextFromIdentifier(
                 schemaContext,
-                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                MOUNT_POINT_IDENT
                 + "/"
                 + TEST_MODULE_NAME
                 + "/"
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json
new file mode 100644 (file)
index 0000000..1483920
--- /dev/null
@@ -0,0 +1,43 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+    "patch-id" : "Test merge operation",
+    "comment" : "This is test patch for merge operation on container",
+    "edit" : [
+      {
+        "edit-id": "edit1",
+        "operation": "create",
+        "target": "/",
+        "value": {
+          "patch-cont": {
+            "my-list1": [
+              {
+                "name": "my-list1 - A",
+                "my-leaf11": "I am leaf11-0",
+                "my-leaf12": "I am leaf12-1"
+              },
+              {
+                "name": "my-list1 - B",
+                "my-leaf11": "I am leaf11-0",
+                "my-leaf12": "I am leaf12-1"
+              }
+            ]
+          }
+        }
+      },
+      {
+        "edit-id": "edit2",
+        "operation": "merge",
+        "target": "/",
+        "value": {
+          "patch-cont": {
+            "my-list1": {
+              "name": "my-list1 - Merged",
+              "my-leaf11": "I am leaf11-0",
+              "my-leaf12": "I am leaf12-1"
+            }
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnList.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHMergeOperationOnList.json
new file mode 100644 (file)
index 0000000..3b809e0
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+    "patch-id" : "Test merge operation",
+    "comment" : "This is test patch for merge operation on list",
+    "edit" : [
+      {
+        "edit-id": "edit1",
+        "operation": "replace",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
+        "value": {
+          "my-list2": {
+            "name": "my-leaf20",
+            "my-leaf21": "I am leaf21-0",
+            "my-leaf22": "I am leaf22-0"
+          }
+        }
+      },
+      {
+        "edit-id": "edit2",
+        "operation": "merge",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf21']",
+        "value": {
+          "my-list2": {
+            "name": "my-leaf21",
+            "my-leaf21": "I am leaf21-1",
+            "my-leaf22": "I am leaf22-1"
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
index cf1530ec4c437e8d58ba4929409be70bb9f6ae1f..e254502ff8e73831bc56f5e2cc811221f794a5ca 100644 (file)
@@ -6,8 +6,8 @@
     "edit" : [
       {
         "edit-id": "edit1",
-        "operation": "create",
-        "target": "/my-list2",
+        "operation": "replace",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
         "value": {
           "my-list2": {
             "name": "my-leaf20",
 
       {
         "edit-id": "edit2",
-        "operation": "create",
-        "target": "/my-list2",
+        "operation": "replace",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
         "value": {
           "my-list2": {
-            "name": "my-leaf21",
+            "name": "my-leaf20",
             "my-leaf21": "I am leaf21-1",
             "my-leaf22": "I am leaf22-1"
           }
@@ -31,4 +31,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json
new file mode 100644 (file)
index 0000000..1b170c7
--- /dev/null
@@ -0,0 +1,44 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+
+    "patch-id" : "test-patch",
+    "comment" : "Test to create and replace data in container directly using / sign as a target",
+    "edit" : [
+      {
+        "edit-id": "edit1",
+        "operation": "create",
+        "target": "/",
+        "value": {
+          "patch-cont": {
+            "my-list1": [
+              {
+                "name": "my-list1 - A",
+                "my-leaf11": "I am leaf11-0",
+                "my-leaf12": "I am leaf12-1"
+              },
+              {
+                "name": "my-list1 - B",
+                "my-leaf11": "I am leaf11-0",
+                "my-leaf12": "I am leaf12-1"
+              }
+            ]
+          }
+        }
+      },
+      {
+        "edit-id": "edit2",
+        "operation": "replace",
+        "target": "/",
+        "value": {
+          "patch-cont": {
+            "my-list1": {
+              "name": "my-list1 - Replacing",
+              "my-leaf11": "I am leaf11-0",
+              "my-leaf12": "I am leaf12-1"
+            }
+          }
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json
new file mode 100644 (file)
index 0000000..4455038
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+    "patch-id" : "test-patch",
+    "comment" : "this is test patch",
+    "edit" : [
+      {
+        "edit-id": "edit1",
+        "value": {
+          "my-list2": [
+            {
+              "name": "my-leaf20",
+              "my-leaf21": "I am leaf20"
+            },
+            {
+              "name": "my-leaf21",
+              "my-leaf21": "I am leaf21-1",
+              "my-leaf22": "I am leaf21-2"
+            }
+          ]
+        },
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
+        "operation": "create"
+      },
+      {
+        "edit-id": "edit2",
+        "operation": "delete",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueMissing.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueMissing.json
new file mode 100644 (file)
index 0000000..eaf1b37
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+    "patch-id" : "test-patch",
+    "comment" : "this is test patch",
+    "edit" : [
+      {
+        "edit-id": "edit1",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
+        "operation": "create"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueNotSupported.json b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/json/jsonPATCHdataValueNotSupported.json
new file mode 100644 (file)
index 0000000..1ad52fb
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "ietf-yang-patch:yang-patch" : {
+    "patch-id" : "test-patch",
+    "comment" : "this is test patch",
+    "edit" : [
+      {
+        "edit-id": "edit2",
+        "operation": "delete",
+        "target": "/instance-identifier-patch-module:my-list2[instance-identifier-patch-module:name='my-leaf20']",
+        "value": {
+          "my-list2": [
+            {
+              "name": "my-leaf20"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml
new file mode 100644 (file)
index 0000000..6e84c47
--- /dev/null
@@ -0,0 +1,35 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>test-patch</patch-id>
+    <comment>Test patch with absolute target path</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>create</operation>
+        <target>/instance-identifier-patch-module:patch-cont/my-list1/leaf1/my-list2</target>
+        <value>
+            <my-list2 xmlns="instance:identifier:patch:module">
+                <name>my-leaf20</name>
+                <my-leaf21>I am leaf21-0</my-leaf21>
+                <my-leaf22>I am leaf22-0</my-leaf22>
+            </my-list2>
+        </value>
+    </edit>
+    <edit>
+        <edit-id>edit2</edit-id>
+        <operation>create</operation>
+        <target>/instance-identifier-patch-module:patch-cont/my-list1/leaf1/my-list2</target>
+        <value>
+            <my-list2 xmlns="instance:identifier:patch:module">
+                <name>my-leaf21</name>
+                <my-leaf21>I am leaf21-1</my-leaf21>
+                <my-leaf22>I am leaf22-1</my-leaf22>
+            </my-list2>
+        </value>
+    </edit>
+</yang-patch>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml
new file mode 100644 (file)
index 0000000..23d2ce0
--- /dev/null
@@ -0,0 +1,44 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>test-patch</patch-id>
+    <comment>Test to create and replace data in container directly using / sign as a target</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>create</operation>
+        <target>/</target>
+        <value>
+            <patch-cont xmlns="instance:identifier:patch:module">
+                <my-list1>
+                    <name>my-list1 - A</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+                <my-list1>
+                    <name>my-list1 - B</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+            </patch-cont>
+        </value>
+    </edit>
+    <edit>
+        <edit-id>edit2</edit-id>
+        <operation>replace</operation>
+        <target>/</target>
+        <value>
+            <patch-cont xmlns="instance:identifier:patch:module">
+                <my-list1>
+                    <name>my-list1 - Replacing</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+            </patch-cont>
+        </value>
+    </edit>
+</yang-patch>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml
new file mode 100644 (file)
index 0000000..afa35bd
--- /dev/null
@@ -0,0 +1,44 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>Test merge operation</patch-id>
+    <comment>This is test patch for merge operation on container</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>create</operation>
+        <target>/</target>
+        <value>
+            <patch-cont xmlns="instance:identifier:patch:module">
+                <my-list1>
+                    <name>my-list1 - A</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+                <my-list1>
+                    <name>my-list1 - B</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+            </patch-cont>
+        </value>
+    </edit>
+    <edit>
+        <edit-id>edit2</edit-id>
+        <operation>merge</operation>
+        <target>/</target>
+        <value>
+            <patch-cont xmlns="instance:identifier:patch:module">
+                <my-list1>
+                    <name>my-list1 - Merged</name>
+                    <my-leaf11>I am leaf11-0</my-leaf11>
+                    <my-leaf12>I am leaf12-1</my-leaf12>
+                </my-list1>
+            </patch-cont>
+        </value>
+    </edit>
+</yang-patch>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml
new file mode 100644 (file)
index 0000000..ad13041
--- /dev/null
@@ -0,0 +1,35 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>Test merge operation</patch-id>
+    <comment>This is test patch for merge operation on list</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>replace</operation>
+        <target>/my-list2</target>
+        <value>
+            <my-list2 xmlns="instance:identifier:patch:module">
+                <name>my-leaf20</name>
+                <my-leaf21>I am leaf21-0</my-leaf21>
+                <my-leaf22>I am leaf22-0</my-leaf22>
+            </my-list2>
+        </value>
+    </edit>
+    <edit>
+        <edit-id>edit2</edit-id>
+        <operation>merge</operation>
+        <target>/my-list2</target>
+        <value>
+            <my-list2 xmlns="instance:identifier:patch:module">
+                <name>my-leaf21</name>
+                <my-leaf21>I am leaf21-1</my-leaf21>
+                <my-leaf22>I am leaf22-1</my-leaf22>
+            </my-list2>
+        </value>
+    </edit>
+</yang-patch>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueMissing.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueMissing.xml
new file mode 100644 (file)
index 0000000..eeec5ad
--- /dev/null
@@ -0,0 +1,16 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>test-patch</patch-id>
+    <comment>Test patch with missing value node for create operation</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>create</operation>
+        <target>/my-list2</target>
+    </edit>
+</yang-patch>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml
new file mode 100644 (file)
index 0000000..8817094
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+  ~ 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
+  -->
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+    <patch-id>test-patch</patch-id>
+    <comment>Test patch with not allowed value node for delete operation</comment>
+    <edit>
+        <edit-id>edit1</edit-id>
+        <operation>delete</operation>
+        <target>/my-list2/my-leaf21</target>
+        <value>
+            <my-list2 xmlns="instance:identifier:patch:module">
+                <name>my-leaf20</name>
+                <my-leaf21>I am leaf21-0</my-leaf21>
+                <my-leaf22>I am leaf22-0</my-leaf22>
+            </my-list2>
+        </value>
+    </edit>
+</yang-patch>
\ No newline at end of file
index 0387d0bfdaf62cb7fbffaa9138bb45235fa02dfd..9fd45d9b0dfc20189e6edefa674d65607d2c07ab 100644 (file)
     <parent>
         <groupId>org.opendaylight.odlparent</groupId>
         <artifactId>odlparent</artifactId>
-        <version>1.7.0-SNAPSHOT</version>
+        <version>1.8.0-SNAPSHOT</version>
         <relativePath/>
     </parent>
 
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>sal-rest-docgen-maven</artifactId>
-    <version>1.4.0-SNAPSHOT</version>
+    <version>1.5.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <dependencyManagement>
@@ -34,7 +34,7 @@
             <dependency>
                 <groupId>org.opendaylight.netconf</groupId>
                 <artifactId>netconf-parent</artifactId>
-                <version>1.1.0-SNAPSHOT</version>
+                <version>1.2.0-SNAPSHOT</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
index 8ca8a077bd97d4222becf6b41c6a8daee7c7191d..d48dec09b4017bc71540760996afde7997d9cfaa 100644 (file)
@@ -4,13 +4,13 @@
   <parent>
     <groupId>org.opendaylight.odlparent</groupId>
     <artifactId>bundle-parent</artifactId>
-    <version>1.7.0-SNAPSHOT</version>
+    <version>1.8.0-SNAPSHOT</version>
     <relativePath/>
   </parent>
 
   <groupId>org.opendaylight.netconf</groupId>
   <artifactId>sal-rest-docgen</artifactId>
-  <version>1.4.0-SNAPSHOT</version>
+  <version>1.5.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
   <dependencyManagement>
@@ -18,7 +18,7 @@
       <dependency>
         <groupId>org.opendaylight.netconf</groupId>
         <artifactId>netconf-parent</artifactId>
-        <version>1.1.0-SNAPSHOT</version>
+        <version>1.2.0-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
index 3e3a0c4bafb84ee4c908a564ddbb585863298115..6cafc73c7d16f21dd297b0b5cc4b267982d9735c 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>org.opendaylight.netconf</groupId>
     <artifactId>restconf-parent</artifactId>
-    <version>1.4.0-SNAPSHOT</version>
+    <version>1.5.0-SNAPSHOT</version>
   </parent>
   <artifactId>sal-restconf-broker</artifactId>
   <packaging>bundle</packaging>