<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
+/*
+ * 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;
}
return reactor.buildEffective();
}
-}
\ No newline at end of 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-monitoring</artifactId>
- <version>1.1.0-SNAPSHOT</version>
+ <version>1.2.0-SNAPSHOT</version>
<name>${project.artifactId}</name>
<packaging>bundle</packaging>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<required-capabilities>
<capability>urn:opendaylight:params:xml:ns:yang:controller:netconf:topology?module=netconf-topology&revision=2015-07-27</capability>
<capability>urn:opendaylight:params:xml:ns:yang:controller:clustered:netconf:topology?module=clustered-netconf-topology&revision=2015-11-04</capability>
- <capability>urn:opendaylight:params:xml:ns:yang:controller:config:distributed-entity-ownership-service?module=distributed-entity-ownership-service&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&revision=2016-02-26
+ </capability>
<capability>urn:opendaylight:params:xml:ns:yang:controller:config:actor-system-provider:service?module=actor-system-provider-service&revision=2015-10-05</capability>
</required-capabilities>
</snapshot>
\ No newline at end of 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-topology</artifactId>
- <version>1.1.0-SNAPSHOT</version>
+ <version>1.2.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<dependencyManagement>
<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>
<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>
<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>
<parent>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-parent</artifactId>
- <version>0.5.0-SNAPSHOT</version>
+ <version>0.6.0-SNAPSHOT</version>
<relativePath/>
</parent>
<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>
<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>
+/*
+ * 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;
verify(listener).sendRequest(msgCaptor.capture(), qNameCaptor.capture());
System.out.println(XmlUtil.toString(msgCaptor.getValue().getDocument()));
}
-}
\ No newline at end of file
+}
+/*
+ * 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;
public void testGetSourceNotAvailable() throws Exception {
yangLibrarySchemaYangSourceProvider.getSource(RevisionSourceIdentifier.create("aaaaa", "0000-00-00"));
}
-}
\ No newline at end of file
+}
+/*
+ * 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;
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
+}
<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>
<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>
<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>
<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>
<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>
<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>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>checkstyle</artifactId>
- <version>0.3.0-SNAPSHOT</version>
+ <version>0.4.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
<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>
<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>
<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>
<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>
<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>
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";
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() {
}
protected UriInfo getUriInfo() {
- return uriInfo;
+ return this.uriInfo;
}
protected boolean isPost() {
- return POST.equals(request.getMethod());
+ return POST.equals(this.request.getMethod());
}
}
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;
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) {
}
}
- 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. ");
}
}
}
- 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);
}
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()) {
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;
+ }
}
}
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);
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();
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();
}
* 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;
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;
}
@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());
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());
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();
--- /dev/null
+/*
+ * 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
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;
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> {
}
@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();
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("]");
+ }
+ }
}
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;
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;
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;
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) {
}
private void checkPreconditions() {
- if (context == null || domDataBroker == null) {
+ if ((this.context == null) || (this.domDataBroker == null)) {
throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
}
}
// 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) {
// 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) {
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);
}
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) {
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));
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));
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));
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(
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);
// 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,
}
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);
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(
}
}
- 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(
}
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(
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);
}
}
package org.opendaylight.netconf.sal.restconf.impl;
+import javax.annotation.Nonnull;
+
/**
*
* Each YANG patch edit specifies one edit operation on the target data
* 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
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);
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;
}
--- /dev/null
+/*
+ * 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;
+ }
+}
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
* @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);
}
* @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));
}
/**
* @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.
* @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);
/**
* 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));
}
* @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));
return errorList;
}
-
public List<RestconfError> getErrors() {
return errors;
}
* 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.
*/
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 */
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) {
}
}
- 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 */),
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) {
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
* @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);
}
/**
* 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);
}
/**
* 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;
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());
}
this.errorInfo = errorInfo;
+ this.errorPath = null;
}
public ErrorType getErrorType() {
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() : "")
+ + "]";
}
-
}
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;
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;
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;
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;
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");
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());
}
}
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;
* 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,
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);
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
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.
*
*/
@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);
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
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.
*/
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;
+ }
+ }
}
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;
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
@Override
public void close() {
- if (listenerRegistration != null) {
- listenerRegistration.close();
+ if (this.listenerRegistration != null) {
+ this.listenerRegistration.close();
}
WebSocketServer.destroyInstance();
- webSocketServerThread.interrupt();
+ this.webSocketServerThread.interrupt();
}
@Override
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;
@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;
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;
*/
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
final String xml = prepareXmlFrom(change);
final Event event = new Event(EventType.NOTIFY);
event.setData(xml);
- eventBus.post(event);
+ this.eventBus.post(event);
}
}
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);
}
}
}
* @return Channel
*/
public Channel getSubscriber() {
- return subscriber;
+ return this.subscriber;
}
/**
* @return String representation of event data.
*/
public String getData() {
- return data;
+ return this.data;
}
/**
* @return The type of the event.
*/
public EventType getType() {
- return type;
+ return this.type;
}
}
* 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();
*/
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) {
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);
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);
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();
* @return Path pointed to data in data store.
*/
public YangInstanceIdentifier getPath() {
- return path;
+ return this.path;
}
/**
* @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);
}
/**
* @return True if exist, false otherwise.
*/
public boolean isListening() {
- return registration == null ? false : true;
+ return this.registration == null ? false : true;
}
/**
}
final Event event = new Event(EventType.REGISTER);
event.setSubscriber(subscriber);
- eventBus.post(event);
+ this.eventBus.post(event);
}
/**
LOG.debug("Subscriber {} is removed.", subscriber.remoteAddress());
final Event event = new Event(EventType.DEREGISTER);
event.setSubscriber(subscriber);
- eventBus.post(event);
+ this.eventBus.post(event);
}
/**
* @return True if exist at least one {@link Channel} subscriber, false otherwise.
*/
public boolean hasSubscribers() {
- return !subscribers.isEmpty();
+ return !this.subscribers.isEmpty();
}
/**
--- /dev/null
+/*
+ * 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;
+ }
+}
*/
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
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() {
* 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);
}
* @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);
}
* 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);
* 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;
}
* 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 {
* @param listener
* ListenerAdapter
*/
- public static void removeListenerIfNoSubscriberExists(ListenerAdapter listener) {
+ public static void removeListenerIfNoSubscriberExists(final ListenerAdapter listener) {
if (!listener.hasSubscribers()) {
deleteListener(listener);
}
* @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();
}
}
+ /**
+ * 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);
+ }
}
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;
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;
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);
}
}
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);
}
}
*/
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()));
@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();
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 {
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();
}
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() + ".");
}
}
}
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) {
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++) {
// 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);
}
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;
schemaContextRef.get());
final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
- DOMDataReadWriteTransaction transaction = null;
+ final DOMDataReadWriteTransaction transaction;
if (mountPoint == null) {
transaction = this.transactionChainHandler.get().newReadWriteTransaction();
} else {
@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);
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;
* - 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());
}
/**
- * 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();
}
}
@Override
public void onFailure(final Throwable t) {
- handlingLoggerAndValues(t, txType, null, null);
responseWaiter.countDown();
+ handlingLoggerAndValues(t, txType, null, null);
}
@Override
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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;
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;
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}
*
return uriBuilder.build();
}
}
-
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;
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);
}
* @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) {
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.");
+ }
+ }
}
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;
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
*/
public final class TransactionUtil {
+ private final static Logger LOG = LoggerFactory.getLogger(TransactionUtil.class);
+
private TransactionUtil() {
throw new UnsupportedOperationException("Util class");
}
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);
+ }
+ }
}
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;
* - {@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 {
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);
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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("]");
+ }
+ }
+}
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" {
}
}
}
+ 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";
+ }
+ }
+
}
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;
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;
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);
}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
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.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 {
.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);
+ }
}
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 {
.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);
+ }
}
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;
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;
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);
}
/**
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;
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;
* @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
*/
@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);
@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
@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);
}
}
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;
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;
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;
@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");
@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");
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);
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")
@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));
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";
}
@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)
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;
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());
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;
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;
@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");
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");
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");
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);
}
}
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;
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;
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;
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;
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));
}
@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);
@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);
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));
}
}
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(
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;
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;
final Set<Module> allModules = schemaContext.getModules();
assertNotNull(allModules);
- controllerContext = spy(ControllerContext.getInstance());
+ controllerContext = ControllerContext.getInstance();
controllerContext.setSchemas(schemaContext);
-
}
@Before
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();
+ }
}
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;
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;
}
/**
- * 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());
}
/**
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;
/**
@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",
@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));
@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));
@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));
@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));
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));
@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));
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>.
@Test
public void deserializeNullSchemaContextNegativeTest() {
thrown.expect(NullPointerException.class);
- YangInstanceIdentifierDeserializer.create(null, "/deserializer-test:contA");
+ YangInstanceIdentifierDeserializer.create(null, "deserializer-test:contA");
}
/**
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>.
@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");
}
/**
@Test
public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA/");
}
/**
@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, "/");
}
/**
@Test
public void prepareQnameTwoSlashesNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA//leaf-A");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA//leaf-A");
}
/**
@Test
public void prepareQnameBuildPathNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test*contA");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test*contA");
}
/**
@Test
public void prepareQnameNotExistingPrefixNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/not-existing:contA");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "not-existing:contA");
}
/**
@Test
public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:*not-parsable-identifier");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:*not-parsable-identifier");
}
/**
@Test
public void prepareQnameErrorParsingNegativeTest() {
thrown.expect(StringIndexOutOfBoundsException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:");
}
/**
@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",
@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",
@Test
public void prepareIdentifierNotKeyedEntryNegativeTest() {
thrown.expect(IllegalArgumentException.class);
- YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key");
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:list-one-key");
}
/**
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,");
}
/**
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));
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",
}
}
- /**
- * 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));
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));
@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",
@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));
final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
assertEquals("Serialization not successful",
- "/serializer-test:contA", result);
+ "serializer-test:contA", result);
}
/**
.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);
}
/**
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);
}
.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);
}
/**
.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);
}
/**
.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);
}
/**
.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);
}
/**
.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);
}
/**
/**
* 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());
}
/**
.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);
}
/**
.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);
}
/**
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);
}
/**
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";
--- /dev/null
+/*
+ * 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
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/"
// 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"
// 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;
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;
}
/**
- * 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());
}
/**
}
/**
- * 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>.
*/
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();
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);
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");
}
}
- /**
- * 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.
thrown.expect(NullPointerException.class);
ParserIdentifier.toSchemaExportContextFromIdentifier(
null,
- MOUNT_POINT_IDENT_WITHOUT_SLASH
+ MOUNT_POINT_IDENT
+ "/"
+ TEST_MODULE_NAME
+ "/"
thrown.expect(NullPointerException.class);
ParserIdentifier.toSchemaExportContextFromIdentifier(
schemaContext,
- MOUNT_POINT_IDENT_WITHOUT_SLASH
+ MOUNT_POINT_IDENT
+ "/"
+ TEST_MODULE_NAME
+ "/"
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
"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"
}
}
]
}
-}
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+<!--
+ ~ 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
--- /dev/null
+<!--
+ ~ 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
--- /dev/null
+<!--
+ ~ 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
--- /dev/null
+<!--
+ ~ 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
--- /dev/null
+<!--
+ ~ 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
--- /dev/null
+<!--
+ ~ 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
<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>
<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>
<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>
<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>
<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>