<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>${bundle.plugin.version}</version>
<extensions>true</extensions>
<configuration>
<instructions>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-parent</artifactId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <artifactId>sal-rest-connector</artifactId>
- <scm>
- <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
- <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
- <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
- </scm>
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>sal-rest-connector</artifactId>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+ <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
+ </scm>
- <dependencies>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sal-connector-api</artifactId>
- </dependency>
- </dependencies>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sal-core-api</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sal-connector-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.xtend</groupId>
+ <artifactId>org.eclipse.xtend.lib</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <version>3.0.4.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
- <packaging>bundle</packaging>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.xtend</groupId>
+ <artifactId>xtend-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>MD SAL Restconf Connector</Bundle-Name>
+ <Private-Package>
+ org.opendaylight.controller.sal.rest.*,
+ org.opendaylight.controller.sal.restconf.impl,
+ org.eclipse.xtend2.lib,
+ org.eclipse.xtend.lib,
+ org.eclipse.xtext.xbase.*
+ </Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <packaging>bundle</packaging>
</project>
--- /dev/null
+package org.opendaylight.controller.sal.restconf.impl
+
+import org.opendaylight.controller.md.sal.common.api.data.DataReader
+import java.net.URI
+import org.opendaylight.yangtools.yang.data.api.CompositeNode
+import org.opendaylight.controller.sal.core.api.data.DataBrokerService
+import org.opendaylight.controller.sal.core.api.model.SchemaService
+import static com.google.common.base.Preconditions.*;
+import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession
+import org.opendaylight.yangtools.yang.common.RpcResult
+import org.opendaylight.controller.md.sal.common.api.data.DataModificationTransactionFactory
+
+class BrokerFacade implements DataReader<String, CompositeNode> {
+
+ @Property
+ private ConsumerSession context;
+
+ @Property
+ private DataBrokerService dataService;
+
+ @Property
+ private SchemaService schemaService;
+
+ @Property
+ private extension ControllerContext schemaContext;
+
+
+ def void init() {
+ checkState(dataService !== null)
+ checkState(schemaService !== null)
+ schemaContext = new ControllerContext();
+ schemaContext.schemas = schemaService.globalContext;
+ }
+
+ override readConfigurationData(String path) {
+ val processedPath = path.removePrefixes();
+ return dataService.readConfigurationData(processedPath.toInstanceIdentifier);
+ }
+
+ override readOperationalData(String path) {
+ val processedPath = path.removePrefixes();
+ return dataService.readOperationalData(processedPath.toInstanceIdentifier);
+ }
+
+ def RpcResult<CompositeNode> invokeRpc(String type,CompositeNode payload) {
+ val future = context.rpc(type.toRpcQName(),payload);
+ return future.get;
+ }
+
+ def commitConfigurationDataUpdate(String path, CompositeNode payload) {
+ val transaction = dataService.beginTransaction;
+ transaction.putConfigurationData(path.toInstanceIdentifier,payload);
+ return transaction.commit()
+ }
+
+ def commitConfigurationDataCreate(String path, CompositeNode payload) {
+ val transaction = dataService.beginTransaction;
+ transaction.putConfigurationData(path.toInstanceIdentifier,payload);
+ return transaction.commit()
+ }
+
+ private def String removePrefixes(String path) {
+ return path;
+ }
+}
--- /dev/null
+package org.opendaylight.controller.sal.restconf.impl
+
+import org.opendaylight.yangtools.yang.model.api.SchemaContext
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates
+import java.net.URI
+import java.util.Map
+import java.util.HashMap
+import org.opendaylight.yangtools.yang.common.QName
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
+
+import java.net.URLEncoder
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
+import java.util.List
+import static com.google.common.base.Preconditions.*;
+import com.google.common.collect.Collections2
+import com.google.common.collect.BiMap
+import com.google.common.collect.HashBiMap
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
+import java.net.URLDecoder
+
+class ControllerContext {
+
+ @Property
+ SchemaContext schemas;
+
+ private val BiMap<URI, String> uriToModuleName = HashBiMap.create();
+ private val Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
+
+ public def InstanceIdentifier toInstanceIdentifier(String restconfInstance) {
+ val ret = InstanceIdentifier.builder();
+ val pathArgs = restconfInstance.split("/");
+ val first = pathArgs.get(0);
+ val startModule = first.toModuleName();
+ val module = schemas.findModuleByNamespace(moduleNameToUri.get(startModule));
+ checkArgument(module.size == 1); // Only one version supported now
+ ret.collectPathArguments(pathArgs, module.iterator.next);
+ return ret.toInstance
+ }
+
+ def String toFullRestconfIdentifier(InstanceIdentifier path) {
+ val elements = path.path;
+ val ret = new StringBuilder();
+ val startQName = elements.get(0).nodeType;
+ val initialModule = schemas.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision)
+ var node = initialModule as DataSchemaNode;
+ for (element : elements) {
+ node = node.childByQName(element.nodeType);
+ ret.append(element.toRestconfIdentifier(node));
+ }
+ return ret.toString
+ }
+
+ private def dispatch CharSequence toRestconfIdentifier(NodeIdentifier argument, DataSchemaNode node) {
+ '''/«argument.nodeType.toRestconfIdentifier()»'''
+ }
+
+ private def dispatch CharSequence toRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) {
+ val nodeIdentifier = argument.nodeType.toRestconfIdentifier();
+ val keyValues = argument.keyValues;
+ return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»'''
+ }
+
+ private def dispatch CharSequence toRestconfIdentifier(PathArgument argument, DataSchemaNode node) {
+ throw new IllegalArgumentException("Conversion of generic path argument is not supported");
+ }
+
+ public def CharSequence toRestconfIdentifier(QName qname) {
+ var module = uriToModuleName.get(qname.namespace)
+ if (module == null) {
+ val moduleSchema = schemas.findModuleByNamespaceAndRevision(qname.namespace, qname.revision);
+ if(moduleSchema == null) throw new IllegalArgumentException()
+ uriToModuleName.put(qname.namespace, moduleSchema.name)
+ module = moduleSchema.name;
+ }
+ return '''«module»:«qname.localName»''';
+ }
+
+ private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) {
+ for (caze : container.cases) {
+ val ret = caze.childByQName(name)
+ if (ret !== null) {
+ return ret;
+ }
+ }
+ return null;
+ }
+
+ private static dispatch def DataSchemaNode childByQName(ChoiceCaseNode container, QName name) {
+ val ret = container.getDataChildByName(name);
+ return ret;
+ }
+
+ private static dispatch def DataSchemaNode childByQName(ContainerSchemaNode container, QName name) {
+ return container.dataNodeChildByQName(name);
+ }
+
+ private static dispatch def DataSchemaNode childByQName(ListSchemaNode container, QName name) {
+ return container.dataNodeChildByQName(name);
+ }
+
+ private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) {
+ return null;
+ }
+
+ private static def DataSchemaNode dataNodeChildByQName(DataNodeContainer container, QName name) {
+ var ret = container.getDataChildByName(name);
+ if (ret === null) {
+
+ // Find in Choice Cases
+ for (node : container.childNodes) {
+ if (node instanceof ChoiceCaseNode) {
+ val caseNode = (node as ChoiceCaseNode);
+ ret = caseNode.childByQName(name);
+ if (ret !== null) {
+ return ret;
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+ private def toUriString(Object object) {
+ if(object == null) return "";
+ return URLEncoder.encode(object.toString)
+ }
+
+ def void collectPathArguments(InstanceIdentifierBuilder builder, List<String> strings, DataNodeContainer parentNode) {
+ checkNotNull(strings)
+ if (strings.length == 0) {
+ return;
+ }
+ val nodeRef = strings.get(0);
+
+ //val moduleName = nodeRef.toModuleName();
+ val nodeName = nodeRef.toNodeName();
+ val naiveTargetNode = parentNode.getDataChildByName(nodeName);
+
+ //var URI namespace;
+ var DataSchemaNode targetNode = naiveTargetNode;
+
+ /*if(moduleName !== null) {
+ namespace = moduleNameToUri.get(moduleName);
+
+ }*/
+ // Number of consumed elements
+ var consumed = 1;
+ if (targetNode instanceof ListSchemaNode) {
+ val listNode = targetNode as ListSchemaNode;
+ val keysSize = listNode.keyDefinition.size
+ val uriKeyValues = strings.subList(1, keysSize);
+ val keyValues = new HashMap<QName, Object>();
+ var i = 0;
+ for (key : listNode.keyDefinition) {
+ val uriKeyValue = uriKeyValues.get(i);
+ keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue);
+ i = i + 1;
+ }
+ consumed = consumed + i;
+ builder.nodeWithKey(targetNode.QName, keyValues);
+ } else {
+
+ // Only one instance of node is allowed
+ builder.node(targetNode.QName);
+ }
+ if (targetNode instanceof DataNodeContainer) {
+ val remaining = strings.subList(consumed, strings.length - 1);
+ builder.collectPathArguments(remaining, targetNode as DataNodeContainer);
+ }
+ }
+
+ def void addKeyValue(HashMap<QName, Object> map, DataSchemaNode node, String uriValue) {
+ checkNotNull(uriValue);
+ checkArgument(node instanceof LeafSchemaNode);
+ val decoded = URLDecoder.decode(uriValue);
+ map.put(node.QName, decoded);
+
+ }
+
+ def String toModuleName(String str) {
+ if (str.contains(":")) {
+ val args = str.split(":");
+ checkArgument(args.size === 2);
+ return args.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ def String toNodeName(String str) {
+ if (str.contains(":")) {
+ val args = str.split(":");
+ checkArgument(args.size === 2);
+ return args.get(1);
+ } else {
+ return str;
+ }
+ }
+
+ public def QName toRpcQName(String name) {
+
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.restconf.impl;
+
+public class MediaTypes {
+ public static final String API = "application/vnd.yang.api";
+ public static final String DATASTORE = "application/vnd.yang.datastore";
+ public static final String DATA = "application/vnd.yang.data";
+ public static final String EVENT = "application/vnd.yang.event";
+ public static final String OPERATION = "application/vnd.yang.operation";
+ public static final String PATCH = "application/vnd.yang.patch";
+}
--- /dev/null
+package org.opendaylight.controller.sal.restconf.impl
+
+import org.opendaylight.yangtools.yang.data.api.CompositeNode
+
+class RestconfImpl implements RestconfService {
+
+ @Property
+ BrokerFacade broker;
+
+ override readAllData() {
+ return broker.readOperationalData("");
+ }
+
+
+ override getModules() {
+ throw new UnsupportedOperationException("TODO: auto-generated method stub")
+ }
+
+ override getRoot() {
+ throw new UnsupportedOperationException("TODO: auto-generated method stub")
+
+ }
+
+ override readData(String identifier) {
+ return broker.readOperationalData(identifier);
+ }
+
+ override createConfigurationData(String identifier, CompositeNode payload) {
+ return broker.commitConfigurationDataCreate(identifier,payload);
+ }
+
+
+ override updateConfigurationData(String identifier, CompositeNode payload) {
+ return broker.commitConfigurationDataCreate(identifier,payload);
+ }
+
+ override invokeRpc(String identifier, CompositeNode payload) {
+ return broker.invokeRpc(identifier,payload);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2013 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.restconf.impl;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+
+import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.*;
+
+/**
+ * The URI hierarchy for the RESTCONF resources consists of an entry
+ * point container, 3 top-level resources, and 1 field. Refer to
+ * Section 5 for details on each URI.
+ * <ul>
+ * <li><b>/restconf</b> - {@link #getRoot()}
+ * <ul><li><b>/datastore</b> - {@link #readAllData()}
+ * <ul>
+ * <li>/(top-level-data-nodes) (config=true or false)
+ * </ul>
+ * <li>/modules
+ * <ul><li>/module
+ * <li>/name
+ * <li>/revision
+ * <li>/namespace
+ * <li>/feature
+ * <li>/deviation
+ * </ul>
+ * <li>/operations
+ * <ul>
+ * <li>/(custom protocol operations)
+ * </ul>
+ * <li>/version (field)
+ * </ul>
+ */
+@Path("restconf")
+public interface RestconfService {
+
+ public static final String XML = "+xml";
+ public static final String JSON = "+json";
+
+ @GET
+ public Object getRoot();
+
+ @GET
+ @Path("/datastore")
+ @Produces({API+JSON,API+XML})
+ public Object readAllData();
+
+ @GET
+ @Path("/datastore/{identifier}")
+ @Produces({API+XML})
+ public Object readData(@PathParam("identifier") String identifier);
+
+ @PUT
+ @Path("/datastore/{identifier}")
+ @Produces({API+XML})
+ public Object createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
+
+ @POST
+ @Path("/datastore/{identifier}")
+ @Produces({API+XML})
+ public Object updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
+
+
+
+ @GET
+ @Path("/modules")
+ @Produces(API+XML)
+ public Object getModules();
+
+ @POST
+ @Path("/operations/{identifier}")
+ @Produces(API+XML)
+ public RpcResult<CompositeNode> invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload);
+}