mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-17 10:21:52 +02:00
[ONOS-5012] implement RESTconf server
- fix javadoc longer than 80 char limit - fix javadoc that missing @params - chain calls to StringBuilder.append() - combine constant strings in place Change-Id: Ie2ef4fd4c19e955ad2d5a5584f5017a842abb790
This commit is contained in:
parent
e20926ebaa
commit
c58d4be8a5
@ -2,6 +2,7 @@ BUNDLES = [
|
||||
'//protocols/restconf/server/api:onos-protocols-restconf-server-api',
|
||||
'//protocols/restconf/server/restconfmgr:onos-protocols-restconf-server-restconfmgr',
|
||||
'//protocols/restconf/server/rpp:onos-protocols-restconf-server-rpp',
|
||||
'//protocols/restconf/server/utils:onos-protocols-restconf-server-utils',
|
||||
]
|
||||
|
||||
onos_app (
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.api;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated method responds to HTTP PATCH requests.
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@HttpMethod("PATCH")
|
||||
public @interface Patch {
|
||||
}
|
@ -25,58 +25,81 @@ import org.glassfish.jersey.server.ChunkedOutput;
|
||||
public interface RestconfService {
|
||||
/**
|
||||
* Processes a GET request against a data resource. The
|
||||
* target data resource is identified by its URI.
|
||||
* target data resource is identified by its URI. If the
|
||||
* GET operation cannot be fulfilled due to reasons such
|
||||
* as the nonexistence of the target resource, then a
|
||||
* RestconfException exception is raised. The proper
|
||||
* HTTP error status code is enclosed in the exception, so
|
||||
* that the caller may return it to the RESTCONF client to
|
||||
* display.
|
||||
*
|
||||
* @param uri URI of the target data resource
|
||||
* @return JSON representation of the data resource
|
||||
* @throws RestconfException if the GET operation cannot be fulfilled due
|
||||
* reasons such as the nonexistence of the target
|
||||
* resource. The proper HTTP error status code is
|
||||
* enclosed in the exception, so that the caller
|
||||
* may return it to the RESTCONF client
|
||||
* @throws RestconfException if the GET operation cannot be fulfilled
|
||||
*/
|
||||
ObjectNode runGetOperationOnDataResource(String uri) throws RestconfException;
|
||||
ObjectNode runGetOperationOnDataResource(String uri)
|
||||
throws RestconfException;
|
||||
|
||||
/**
|
||||
* Processes a POST request against a data resource. The location of
|
||||
* the target resource is passed in as a URI. And the resource's
|
||||
* content is passed in as a JSON ObjectNode.
|
||||
* content is passed in as a JSON ObjectNode. If the POST operation
|
||||
* cannot be fulfilled due to reasons such as wrong input URIs or
|
||||
* syntax errors in the JSON payloads, a RestconfException exception
|
||||
* is raised. The proper HTTP error status code is enclosed in the
|
||||
* exception.
|
||||
*
|
||||
* @param uri URI of the data resource to be created
|
||||
* @param rootNode JSON representation of the data resource
|
||||
* @throws RestconfException if the POST operation cannot be fulfilled due
|
||||
* reasons such as wrong URI or syntax error
|
||||
* in JSON payload. The proper HTTP error status
|
||||
* code is enclosed in the exception
|
||||
* @throws RestconfException if the POST operation cannot be fulfilled
|
||||
*/
|
||||
void runPostOperationOnDataResource(String uri, ObjectNode rootNode) throws RestconfException;
|
||||
void runPostOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException;
|
||||
|
||||
/**
|
||||
* Processes a PUT request against a data resource. The location of
|
||||
* the target resource is passed in as a URI. And the resource's
|
||||
* content is passed in as a JSON ObjectNode.
|
||||
* content is passed in as a JSON ObjectNode. If the PUT operation
|
||||
* cannot be fulfilled due to reasons such as wrong input URIs or
|
||||
* syntax errors in the JSON payloads, a RestconfException exception
|
||||
* is raised. The proper HTTP error status code is enclosed in the
|
||||
* exception.
|
||||
*
|
||||
* @param uri URI of the data resource to be created or updated
|
||||
* @param rootNode JSON representation of the data resource
|
||||
* @throws RestconfException if the PUT operation cannot be fulfilled due
|
||||
* reasons such as wrong URI or syntax error
|
||||
* in JSON payload. The proper HTTP error status
|
||||
* code is enclosed in the exception
|
||||
* @throws RestconfException if the PUT operation cannot be fulfilled
|
||||
*/
|
||||
void runPutOperationOnDataResource(String uri, ObjectNode rootNode) throws RestconfException;
|
||||
void runPutOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException;
|
||||
|
||||
/**
|
||||
* Processes the DELETE operation against a data resource. The target
|
||||
* data resource is identified by its URI.
|
||||
* data resource is identified by its URI. If the DELETE operation
|
||||
* cannot be fulfilled due reasons such as the nonexistence of the
|
||||
* target resource, a RestconfException exception is raised. The
|
||||
* proper HTTP error status code is enclosed in the exception.
|
||||
*
|
||||
* @param uri URI of the data resource to be deleted
|
||||
* @throws RestconfException if the DELETE operation cannot be fulfilled due
|
||||
* reasons such as the nonexistence of the target
|
||||
* resource. The proper HTTP error status code is
|
||||
* enclosed in the exception
|
||||
* @throws RestconfException if the DELETE operation cannot be fulfilled
|
||||
*/
|
||||
void runDeleteOperationOnDataResource(String uri) throws RestconfException;
|
||||
|
||||
/**
|
||||
* Processes a PATCH operation on a data resource. The target data
|
||||
* resource is identified by its URI passed in by the caller.
|
||||
* And the content of the data resource is passed in as a JSON ObjectNode.
|
||||
* If the PATCH operation cannot be fulfilled due reasons such as
|
||||
* the nonexistence of the target resource, a RestconfException
|
||||
* exception is raised. The proper HTTP error status code is
|
||||
* enclosed in the exception.
|
||||
*
|
||||
* @param uri URI of the data resource to be patched
|
||||
* @param rootNode JSON representation of the data resource
|
||||
* @throws RestconfException if the PATCH operation cannot be fulfilled
|
||||
*/
|
||||
void runPatchOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException;
|
||||
|
||||
/**
|
||||
* Retrieves the RESTCONF Root directory.
|
||||
*
|
||||
@ -90,13 +113,18 @@ public interface RestconfService {
|
||||
* which is passed in from the caller. (The worker thread blocks if
|
||||
* no events arrive.) The ChuckedOutput is a pipe to which this
|
||||
* function acts as the writer and the caller the reader.
|
||||
* <p>
|
||||
* If the Event Stream cannot be subscribed due to reasons such as
|
||||
* the nonexistence of the target stream or failure to allocate
|
||||
* worker thread to handle the request, a RestconfException exception
|
||||
* is raised. The proper HTTP error status code is enclosed in the
|
||||
* exception, so that the caller may return it to the RESTCONF client
|
||||
* to display.
|
||||
*
|
||||
* @param streamId ID of the RESTCONF stream to subscribe
|
||||
* @param output A string data stream
|
||||
* @throws RestconfException if the Event Stream cannot be subscribed due to
|
||||
* reasons such as the nonexistence of the target
|
||||
* stream or unable to allocate any free worker
|
||||
* thread to handle the request
|
||||
* @throws RestconfException if the Event Stream cannot be subscribed
|
||||
*/
|
||||
void subscribeEventStream(String streamId, ChunkedOutput<String> output) throws RestconfException;
|
||||
void subscribeEventStream(String streamId, ChunkedOutput<String> output)
|
||||
throws RestconfException;
|
||||
}
|
||||
|
@ -22,4 +22,5 @@
|
||||
<artifact>mvn:${project.groupId}/onos-restconf-server-api/${project.version}</artifact>
|
||||
<artifact>mvn:${project.groupId}/onos-restconf-server-restconfmanager/${project.version}</artifact>
|
||||
<artifact>mvn:${project.groupId}/onos-restconf-server-rpp/${project.version}</artifact>
|
||||
<artifact>mvn:${project.groupId}/onos-restconf-server-utils/${project.version}</artifact>
|
||||
</app>
|
||||
|
@ -21,5 +21,6 @@
|
||||
<bundle>mvn:${project.groupId}/onos-restconf-server-api/${project.version}</bundle>
|
||||
<bundle>mvn:${project.groupId}/onos-restconf-server-restconfmanager/${project.version}</bundle>
|
||||
<bundle>mvn:${project.groupId}/onos-restconf-server-rpp/${project.version}</bundle>
|
||||
<bundle>mvn:${project.groupId}/onos-restconf-server-utils/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
|
@ -51,5 +51,10 @@
|
||||
<artifactId>onos-restconf-server-rpp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-restconf-server-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -34,6 +34,7 @@
|
||||
<module>restconfmgr</module>
|
||||
<module>rpp</module>
|
||||
<module>app</module>
|
||||
<module>utils</module>
|
||||
</modules>
|
||||
|
||||
<description>RESTCONF Server Module</description>
|
||||
|
@ -6,6 +6,8 @@ COMPILE_DEPS = [
|
||||
'//utils/rest:onlab-rest',
|
||||
'//core/store/serializers:onos-core-serializers',
|
||||
'//protocols/restconf/server/api:onos-protocols-restconf-server-api',
|
||||
'//protocols/restconf/server/utils:onos-protocols-restconf-server-utils',
|
||||
'//apps/yms/api:onos-apps-yms-api',
|
||||
]
|
||||
|
||||
osgi_jar_with_tests (
|
||||
|
@ -58,6 +58,16 @@
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.scr.annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-restconf-server-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-app-yms-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -15,20 +15,30 @@
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.restconfmanager;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.apache.felix.scr.annotations.Activate;
|
||||
import org.apache.felix.scr.annotations.Component;
|
||||
import org.apache.felix.scr.annotations.Deactivate;
|
||||
import org.apache.felix.scr.annotations.Reference;
|
||||
import org.apache.felix.scr.annotations.ReferenceCardinality;
|
||||
import org.apache.felix.scr.annotations.Service;
|
||||
import org.glassfish.jersey.server.ChunkedOutput;
|
||||
import org.onosproject.event.ListenerTracker;
|
||||
import org.onosproject.protocol.restconf.server.api.RestconfException;
|
||||
import org.onosproject.protocol.restconf.server.api.RestconfService;
|
||||
import org.onosproject.yms.ydt.YdtBuilder;
|
||||
import org.onosproject.yms.ydt.YdtContext;
|
||||
import org.onosproject.yms.ydt.YdtContextOperationType;
|
||||
import org.onosproject.yms.ydt.YdtResponse;
|
||||
import org.onosproject.yms.ydt.YmsOperationExecutionStatus;
|
||||
import org.onosproject.yms.ydt.YmsOperationType;
|
||||
import org.onosproject.yms.ymsm.YmsService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -37,21 +47,37 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
import static org.onosproject.yms.ydt.YmsOperationType.QUERY_REQUEST;
|
||||
import static org.onosproject.yms.ydt.YmsOperationType.EDIT_CONFIG_REQUEST;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.NONE;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.CREATE;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.DELETE;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.REPLACE;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.MERGE;
|
||||
import static org.onosproject.yms.ydt.YdtType.SINGLE_INSTANCE_LEAF_VALUE_NODE;
|
||||
import static org.onosproject.yms.ydt.YmsOperationExecutionStatus.EXECUTION_SUCCESS;
|
||||
import static org.onosproject.protocol.restconf.server.utils.parser.json.ParserUtils.convertYdtToJson;
|
||||
import static org.onosproject.protocol.restconf.server.utils.parser.json.ParserUtils.convertUriToYdt;
|
||||
import static org.onosproject.protocol.restconf.server.utils.parser.json.ParserUtils.convertJsonToYdt;
|
||||
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
/*
|
||||
* Skeletal ONOS RESTCONF Server application. The RESTCONF Manager
|
||||
* implements the main logic of the RESTCONF Server.
|
||||
*
|
||||
* The design of the RESTCONF subsystem contains 2 major bundles:
|
||||
*
|
||||
* 1. RESTCONF Protocol Proxy (RPP). This bundle is implemented as a JAX-RS application.
|
||||
* It acts as the frond-end of the the RESTCONF server. It handles
|
||||
* HTTP requests that are sent to the RESTCONF Root Path. It then calls the RESTCONF Manager
|
||||
* to process the requests.
|
||||
* 1. RESTCONF Protocol Proxy (RPP). This bundle is implemented as a
|
||||
* JAX-RS application. It acts as the frond-end of the RESTCONF server.
|
||||
* It intercepts/handles HTTP requests that are sent to the RESTCONF
|
||||
* Root Path. It then calls the RESTCONF Manager to process the requests.
|
||||
*
|
||||
* 2. RESTCONF Manager. This is the back-end. It provides the main logic of the RESTCONF server.
|
||||
* It calls the YMS (YANG Management System) to operate on the YANG data objects.
|
||||
* 2. RESTCONF Manager. This bundle module is the back-end of the server.
|
||||
* It provides the main logic of the RESTCONF server. It interacts with
|
||||
* the YMS (YANG Management System) to run operations on the YANG data
|
||||
* objects (i.e., data resources).
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -73,26 +99,25 @@ public class RestconfManager implements RestconfService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
//TODO: YMS service
|
||||
//@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
//protected YmsService ymsService;
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected YmsService ymsService;
|
||||
|
||||
private ListenerTracker listeners;
|
||||
|
||||
private ConcurrentMap<String, BlockingQueue<ObjectNode>> eventQueueList =
|
||||
new ConcurrentHashMap<String, BlockingQueue<ObjectNode>>();
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private ExecutorService workerThreadPool;
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
workerThreadPool = Executors.newFixedThreadPool(maxNumOfWorkerThreads,
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("restconf-worker")
|
||||
.build());
|
||||
workerThreadPool = Executors
|
||||
.newFixedThreadPool(maxNumOfWorkerThreads,
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("restconf-worker")
|
||||
.build());
|
||||
listeners = new ListenerTracker();
|
||||
//TODO: YMS notification
|
||||
//listeners.addListener(ymsService, new InternalYangNotificationListener());
|
||||
log.info("Started");
|
||||
}
|
||||
|
||||
@ -104,34 +129,117 @@ public class RestconfManager implements RestconfService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectNode runGetOperationOnDataResource(String uri) throws RestconfException {
|
||||
//TODO: YMS integration
|
||||
return null;
|
||||
public ObjectNode runGetOperationOnDataResource(String uri)
|
||||
throws RestconfException {
|
||||
YdtBuilder ydtBuilder = getYdtBuilder(QUERY_REQUEST);
|
||||
//Convert the URI to ydtBuilder
|
||||
convertUriToYdt(uri, ydtBuilder, NONE);
|
||||
YdtResponse ydtResponse = ymsService.executeOperation(ydtBuilder);
|
||||
YmsOperationExecutionStatus status = ydtResponse
|
||||
.getYmsOperationResult();
|
||||
if (status != EXECUTION_SUCCESS) {
|
||||
throw new RestconfException("YMS GET operation failed",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
YdtContext rootNode = ydtResponse.getRootNode();
|
||||
YdtContext curNode = ydtBuilder.getCurNode();
|
||||
|
||||
ObjectNode result = convertYdtToJson(curNode.getName(), rootNode,
|
||||
ymsService.getYdtWalker());
|
||||
//if the query URI contain a key, something like list=key
|
||||
//here should only get get child with the specific key
|
||||
YdtContext child = curNode.getFirstChild();
|
||||
if (child != null &&
|
||||
child.getYdtType() == SINGLE_INSTANCE_LEAF_VALUE_NODE) {
|
||||
|
||||
ArrayNode jsonNode = (ArrayNode) result.get(curNode.getName());
|
||||
for (JsonNode next : jsonNode) {
|
||||
if (next.findValue(child.getName())
|
||||
.asText().equals(child.getValue())) {
|
||||
return (ObjectNode) next;
|
||||
}
|
||||
}
|
||||
throw new RestconfException(String.format("No content for %s = %s",
|
||||
child.getName(),
|
||||
child.getValue()),
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private YmsOperationExecutionStatus
|
||||
invokeYmsOp(String uri, ObjectNode rootNode,
|
||||
YdtContextOperationType opType) {
|
||||
YdtBuilder ydtBuilder = getYdtBuilder(EDIT_CONFIG_REQUEST);
|
||||
//Convert the URI to ydtBuilder
|
||||
convertUriToYdt(uri, ydtBuilder, opType);
|
||||
|
||||
//set default operation type for the payload node
|
||||
ydtBuilder.setDefaultEditOperationType(opType);
|
||||
//convert the payload json body to ydt
|
||||
convertJsonToYdt(rootNode, ydtBuilder);
|
||||
|
||||
return ymsService
|
||||
.executeOperation(ydtBuilder)
|
||||
.getYmsOperationResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPostOperationOnDataResource(String uri, ObjectNode rootNode) throws RestconfException {
|
||||
//TODO: YMS integration
|
||||
public void runPostOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException {
|
||||
YmsOperationExecutionStatus status =
|
||||
invokeYmsOp(uri, rootNode, CREATE);
|
||||
|
||||
if (status != EXECUTION_SUCCESS) {
|
||||
throw new RestconfException("YMS post operation failed.",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPutOperationOnDataResource(String uri, ObjectNode rootNode) throws RestconfException {
|
||||
//TODO: YMS integration
|
||||
public void runPutOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException {
|
||||
YmsOperationExecutionStatus status =
|
||||
invokeYmsOp(uri, rootNode, REPLACE);
|
||||
|
||||
if (status != EXECUTION_SUCCESS) {
|
||||
throw new RestconfException("YMS put operation failed.",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the delete operation on a data resource.
|
||||
*
|
||||
* @param uri URI of the data resource to be deleted.
|
||||
*/
|
||||
@Override
|
||||
public void runDeleteOperationOnDataResource(String uri) throws RestconfException {
|
||||
//TODO: YMS integration
|
||||
public void runDeleteOperationOnDataResource(String uri)
|
||||
throws RestconfException {
|
||||
//Get a root ydtBuilder
|
||||
YdtBuilder ydtBuilder = getYdtBuilder(EDIT_CONFIG_REQUEST);
|
||||
//Convert the URI to ydtBuilder
|
||||
convertUriToYdt(uri, ydtBuilder, DELETE);
|
||||
//Execute the delete operation
|
||||
YmsOperationExecutionStatus status = ymsService
|
||||
.executeOperation(ydtBuilder)
|
||||
.getYmsOperationResult();
|
||||
if (status != EXECUTION_SUCCESS) {
|
||||
throw new RestconfException("YMS delete operation failed.",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runPatchOperationOnDataResource(String uri, ObjectNode rootNode)
|
||||
throws RestconfException {
|
||||
YmsOperationExecutionStatus status = invokeYmsOp(uri, rootNode, MERGE);
|
||||
|
||||
if (status != EXECUTION_SUCCESS) {
|
||||
throw new RestconfException("YMS patch operation failed.",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRestconfRootPath() {
|
||||
return this.RESTCONF_ROOT;
|
||||
return RESTCONF_ROOT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,16 +251,21 @@ public class RestconfManager implements RestconfService {
|
||||
* @throws RestconfException if the worker thread fails to create
|
||||
*/
|
||||
@Override
|
||||
public void subscribeEventStream(String streamId, ChunkedOutput<String> output) throws RestconfException {
|
||||
BlockingQueue<ObjectNode> eventQueue = new LinkedBlockingQueue<ObjectNode>();
|
||||
public void subscribeEventStream(String streamId,
|
||||
ChunkedOutput<String> output)
|
||||
throws RestconfException {
|
||||
BlockingQueue<ObjectNode> eventQueue = new LinkedBlockingQueue<>();
|
||||
if (workerThreadPool instanceof ThreadPoolExecutor) {
|
||||
if (((ThreadPoolExecutor) workerThreadPool).getActiveCount() >= maxNumOfWorkerThreads) {
|
||||
throw new RestconfException("no more work threads left to handle event subscription",
|
||||
Response.Status.INTERNAL_SERVER_ERROR);
|
||||
if (((ThreadPoolExecutor) workerThreadPool).getActiveCount() >=
|
||||
maxNumOfWorkerThreads) {
|
||||
throw new RestconfException("no more work threads left to " +
|
||||
"handle event subscription",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
throw new RestconfException("Server ERROR: workerThreadPool NOT instanceof ThreadPoolExecutor",
|
||||
Response.Status.INTERNAL_SERVER_ERROR);
|
||||
throw new RestconfException("Server ERROR: workerThreadPool NOT " +
|
||||
"instanceof ThreadPoolExecutor",
|
||||
INTERNAL_SERVER_ERROR);
|
||||
|
||||
}
|
||||
|
||||
@ -169,10 +282,11 @@ public class RestconfManager implements RestconfService {
|
||||
pool.shutdown(); // Disable new tasks from being submitted
|
||||
try {
|
||||
// Wait a while for existing tasks to terminate
|
||||
if (!pool.awaitTermination(THREAD_TERMINATION_TIMEOUT, TimeUnit.SECONDS)) {
|
||||
if (!pool.awaitTermination(THREAD_TERMINATION_TIMEOUT, SECONDS)) {
|
||||
pool.shutdownNow(); // Cancel currently executing tasks
|
||||
// Wait a while for tasks to respond to being cancelled
|
||||
if (!pool.awaitTermination(THREAD_TERMINATION_TIMEOUT, TimeUnit.SECONDS)) {
|
||||
if (!pool.awaitTermination(THREAD_TERMINATION_TIMEOUT,
|
||||
SECONDS)) {
|
||||
log.error("Pool did not terminate");
|
||||
}
|
||||
}
|
||||
@ -190,7 +304,8 @@ public class RestconfManager implements RestconfService {
|
||||
private final ChunkedOutput<String> output;
|
||||
private final BlockingQueue<ObjectNode> bqueue;
|
||||
|
||||
public EventConsumer(ChunkedOutput<String> output, BlockingQueue<ObjectNode> q) {
|
||||
public EventConsumer(ChunkedOutput<String> output,
|
||||
BlockingQueue<ObjectNode> q) {
|
||||
this.queueId = Thread.currentThread().getName();
|
||||
this.output = output;
|
||||
this.bqueue = q;
|
||||
@ -212,7 +327,8 @@ public class RestconfManager implements RestconfService {
|
||||
*/
|
||||
eventQueueList.remove(this.queueId);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("ERROR: EventConsumer: bqueue.take() has been interrupted.");
|
||||
log.error("ERROR: EventConsumer: bqueue.take() " +
|
||||
"has been interrupted.");
|
||||
log.debug("EventConsumer Exception:", e);
|
||||
} finally {
|
||||
try {
|
||||
@ -226,6 +342,10 @@ public class RestconfManager implements RestconfService {
|
||||
|
||||
}
|
||||
|
||||
private YdtBuilder getYdtBuilder(YmsOperationType ymsOperationType) {
|
||||
return ymsService.getYdtBuilder(RESTCONF_ROOT, null, ymsOperationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener class acts as the event producer for the event queues. The
|
||||
* queues are created by the event consumer threads and are removed when the
|
||||
|
@ -19,6 +19,7 @@ package org.onosproject.protocol.restconf.server.rpp;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.glassfish.jersey.server.ChunkedOutput;
|
||||
import org.onosproject.protocol.restconf.server.api.Patch;
|
||||
import org.onosproject.protocol.restconf.server.api.RestconfException;
|
||||
import org.onosproject.protocol.restconf.server.api.RestconfService;
|
||||
import org.onosproject.rest.AbstractWebResource;
|
||||
@ -39,8 +40,11 @@ import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
|
||||
/*
|
||||
* This class is the main implementation of the RESTCONF Protocol
|
||||
* Proxy module. Currently it only handles some basic operations
|
||||
@ -134,7 +138,8 @@ public class RestconfWebResource extends AbstractWebResource {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("data/{identifier : .+}")
|
||||
public Response handlePostRequest(@PathParam("identifier") String uriString, InputStream stream) {
|
||||
public Response handlePostRequest(@PathParam("identifier") String uriString,
|
||||
InputStream stream) {
|
||||
|
||||
log.debug("handlePostRequest: {}", uriString);
|
||||
|
||||
@ -145,14 +150,14 @@ public class RestconfWebResource extends AbstractWebResource {
|
||||
return Response.created(uriInfo.getRequestUri()).build();
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("ERROR: handlePostRequest ", e);
|
||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||
return Response.status(BAD_REQUEST).build();
|
||||
} catch (RestconfException e) {
|
||||
log.error("ERROR: handlePostRequest: {}", e.getMessage());
|
||||
log.debug("Exception in handlePostRequest:", e);
|
||||
return e.getResponse();
|
||||
} catch (IOException ex) {
|
||||
log.error("ERROR: handlePostRequest ", ex);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
||||
return Response.status(INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +179,8 @@ public class RestconfWebResource extends AbstractWebResource {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("data/{identifier : .+}")
|
||||
public Response handlePutRequest(@PathParam("identifier") String uriString, InputStream stream) {
|
||||
public Response handlePutRequest(@PathParam("identifier") String uriString,
|
||||
InputStream stream) {
|
||||
|
||||
log.debug("handlePutRequest: {}", uriString);
|
||||
|
||||
@ -185,14 +191,14 @@ public class RestconfWebResource extends AbstractWebResource {
|
||||
return Response.created(uriInfo.getRequestUri()).build();
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("ERROR: handlePutRequest ", e);
|
||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||
return Response.status(BAD_REQUEST).build();
|
||||
} catch (RestconfException e) {
|
||||
log.error("ERROR: handlePutRequest: {}", e.getMessage());
|
||||
log.debug("Exception in handlePutRequest:", e);
|
||||
return e.getResponse();
|
||||
} catch (IOException ex) {
|
||||
log.error("ERROR: handlePutRequest ", ex);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
||||
return Response.status(INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,4 +229,41 @@ public class RestconfWebResource extends AbstractWebResource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a RESTCONF PATCH operation against a data resource.
|
||||
* If the PATCH request succeeds, a "200 OK" status-line is returned if
|
||||
* there is a message-body, and "204 No Content" is returned if no
|
||||
* response message-body is sent.
|
||||
*
|
||||
* @param uriString URI of the data resource
|
||||
* @param stream Input JSON object
|
||||
* @return HTTP response
|
||||
*/
|
||||
@Patch
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("data/{identifier : .+}")
|
||||
public Response handlePatchRequest(@PathParam("identifier") String uriString,
|
||||
InputStream stream) {
|
||||
|
||||
log.debug("handlePatchRequest: {}", uriString);
|
||||
|
||||
try {
|
||||
ObjectNode rootNode = (ObjectNode) mapper().readTree(stream);
|
||||
|
||||
service.runPatchOperationOnDataResource(uriString, rootNode);
|
||||
return Response.ok().build();
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("ERROR: handlePatchRequest ", e);
|
||||
return Response.status(BAD_REQUEST).build();
|
||||
} catch (RestconfException e) {
|
||||
log.error("ERROR: handlePatchRequest: {}", e.getMessage());
|
||||
log.debug("Exception in handlePatchRequest:", e);
|
||||
return e.getResponse();
|
||||
} catch (IOException ex) {
|
||||
log.error("ERROR: handlePatchRequest ", ex);
|
||||
return Response.status(INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
8
protocols/restconf/server/utils/BUCK
Normal file
8
protocols/restconf/server/utils/BUCK
Normal file
@ -0,0 +1,8 @@
|
||||
COMPILE_DEPS = [
|
||||
'//lib:CORE_DEPS',
|
||||
'//apps/yms/api:onos-apps-yms-api',
|
||||
]
|
||||
|
||||
osgi_jar_with_tests (
|
||||
deps = COMPILE_DEPS,
|
||||
)
|
27
protocols/restconf/server/utils/pom.xml
Normal file
27
protocols/restconf/server/utils/pom.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<parent>
|
||||
<artifactId>onos-restconf-server</artifactId>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<version>1.8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>onos-restconf-server-utils</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-app-yms-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.exceptions;
|
||||
|
||||
/**
|
||||
* Represents class of errors related to Json parse utils.
|
||||
*/
|
||||
public class JsonParseException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs an exception with the specified message.
|
||||
*
|
||||
* @param message the message describing the specific nature of the error
|
||||
*/
|
||||
public JsonParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception with the specified message and the underlying
|
||||
* cause.
|
||||
*
|
||||
* @param message the message describing the specific nature of the error
|
||||
* @param cause the underlying cause of this error
|
||||
*/
|
||||
public JsonParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.exceptions;
|
||||
|
||||
/**
|
||||
* Represents class of errors related to YDT parse utils.
|
||||
*/
|
||||
public class YdtParseException extends RuntimeException {
|
||||
/**
|
||||
* Constructs an exception with the specified message.
|
||||
*
|
||||
* @param message the message describing the specific nature of the error
|
||||
*/
|
||||
public YdtParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception with the specified message and the underlying
|
||||
* cause.
|
||||
*
|
||||
* @param message the message describing the specific nature of the error
|
||||
* @param cause the underlying cause of this error
|
||||
*/
|
||||
public YdtParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse utils custom exceptions.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.exceptions;
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Abstraction of an entity which provides interfaces to build and obtain JSON
|
||||
* data tree.
|
||||
*/
|
||||
public interface JsonBuilder {
|
||||
|
||||
/**
|
||||
* Adds a to half (a left brace/bracket and the field name) of a JSON
|
||||
* object/array to the JSON tree. This method is used by protocols which
|
||||
* knows the nature (object/array) of node.
|
||||
*
|
||||
* @param fieldName name of child to be added
|
||||
* @param nodeType the type of the child
|
||||
*/
|
||||
void addNodeTopHalf(String fieldName, JsonNodeType nodeType);
|
||||
|
||||
/**
|
||||
* Adds a child with value and a comma to the JSON tree.
|
||||
* Protocols unaware of nature of node (single/multiple) will use it to add
|
||||
* both single instance and multi instance node. Protocols aware of nature
|
||||
* of node will use it for single instance value node addition.
|
||||
*
|
||||
* @param fieldName name of child to be added
|
||||
* @param value the type of the child
|
||||
*/
|
||||
void addNodeWithValueTopHalf(String fieldName, String value);
|
||||
|
||||
/**
|
||||
* Adds a child with list of values to JSON data tree. This method is
|
||||
* used by protocols which knows the nature (object/array) of node for
|
||||
* ArrayNode addition.
|
||||
*
|
||||
* @param fieldName name of child to be added
|
||||
* @param sets the value list of the child
|
||||
*/
|
||||
void addNodeWithSetTopHalf(String fieldName, Set<String> sets);
|
||||
|
||||
/**
|
||||
* Adds the bottom half(a right brace/bracket) of a JSON object/array to
|
||||
* the JSON tree. for the text, a comma should be taken out.
|
||||
*
|
||||
* @param nodeType the type of the child
|
||||
*/
|
||||
void addNodeBottomHalf(JsonNodeType nodeType);
|
||||
|
||||
/**
|
||||
* Returns the JSON tree after build operations in the format of string.
|
||||
*
|
||||
* @return the final string JSON tree after build operations
|
||||
*/
|
||||
String getTreeString();
|
||||
|
||||
/**
|
||||
* Returns the JSON tree after build operations in the format of ObjectNode.
|
||||
*
|
||||
* @return the final ObjectNode JSON tree after build operations
|
||||
*/
|
||||
ObjectNode getTreeNode();
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.api;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* Abstraction of an entity which provide call back methods which are called
|
||||
* by JSON walker while walking the JSON data tree. This interface needs to be
|
||||
* implemented by protocol implementing listener's based call backs while JSON
|
||||
* walk.
|
||||
*/
|
||||
public interface JsonListener {
|
||||
|
||||
/**
|
||||
* Callback invoked during a node entry.
|
||||
* All the related information about the node can be obtain from the JSON
|
||||
* object.
|
||||
*
|
||||
* @param fieldName the field name of the JSON Node value
|
||||
* @param node the JsonNode which is walked through
|
||||
*/
|
||||
void enterJsonNode(String fieldName, JsonNode node);
|
||||
|
||||
/**
|
||||
* Callback invoked during a node exit.
|
||||
* All the related information about the node can be obtain from the JSON
|
||||
* node.
|
||||
*
|
||||
* @param jsonNode JSON node which has been walked through
|
||||
*/
|
||||
void exitJsonNode(JsonNode jsonNode);
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* Abstraction of an entity which provides interfaces for Json walk.
|
||||
* This interface serve as common tools for anyone who needs to parse
|
||||
* the json node with depth-first algorithm.
|
||||
*/
|
||||
public interface JsonWalker {
|
||||
|
||||
/**
|
||||
* Walks the JSON data tree. Protocols implements JSON listener service
|
||||
* and walks JSON tree with input as implemented object. JSON walker
|
||||
* provides call backs to implemented methods. For the original json
|
||||
* node(come from NB), there is a field name which is something like the
|
||||
* module name of a YANG model. If not, the fieldName can be null.
|
||||
*
|
||||
* @param jsonListener Json listener implemented by the user
|
||||
* @param fieldName the original object node field
|
||||
* @param node the json node which needs to be walk
|
||||
*/
|
||||
void walk(JsonListener jsonListener, String fieldName, ObjectNode node);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provider json related process interface.
|
||||
*/
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.api;
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.onosproject.protocol.restconf.server.utils.exceptions.JsonParseException;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
/**
|
||||
* Represents implementation of interfaces to build and obtain JSON data tree.
|
||||
*/
|
||||
public class DefaultJsonBuilder implements JsonBuilder {
|
||||
|
||||
private static final String LEFT_BRACE = "{";
|
||||
private static final String RIGHT_BRACE = "}";
|
||||
private static final String LEFT_BRACKET = "[";
|
||||
private static final String RIGHT_BRACKET = "]";
|
||||
private static final String COMMA = ",";
|
||||
private static final String COLON = ":";
|
||||
private static final String QUOTE = "\"";
|
||||
private static final String E_UNSUP_TYPE = "Unsupported node type %s " +
|
||||
"field name is %s fieldName";
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private StringBuilder treeString;
|
||||
|
||||
/**
|
||||
* Creates a Default Json Builder with a specific root name.
|
||||
*
|
||||
* @param rootName the start string of the Json builder
|
||||
*/
|
||||
public DefaultJsonBuilder(String rootName) {
|
||||
checkNotNull(rootName);
|
||||
treeString = new StringBuilder(rootName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Default Json Builder with a default root name.
|
||||
*/
|
||||
public DefaultJsonBuilder() {
|
||||
treeString = new StringBuilder(LEFT_BRACE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNodeTopHalf(String fieldName, JsonNodeType nodeType) {
|
||||
|
||||
appendField(fieldName);
|
||||
|
||||
switch (nodeType) {
|
||||
case OBJECT:
|
||||
treeString.append(LEFT_BRACE);
|
||||
break;
|
||||
case ARRAY:
|
||||
treeString.append(LEFT_BRACKET);
|
||||
break;
|
||||
default:
|
||||
throw new JsonParseException(String.format(E_UNSUP_TYPE,
|
||||
nodeType, fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNodeWithValueTopHalf(String fieldName, String value) {
|
||||
if (isNullOrEmpty(fieldName)) {
|
||||
return;
|
||||
}
|
||||
appendField(fieldName);
|
||||
if (value.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
treeString.append(QUOTE)
|
||||
.append(value)
|
||||
.append(QUOTE + COMMA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNodeWithSetTopHalf(String fieldName, Set<String> sets) {
|
||||
if (isNullOrEmpty(fieldName)) {
|
||||
return;
|
||||
}
|
||||
appendField(fieldName);
|
||||
treeString.append(LEFT_BRACKET);
|
||||
for (String el : sets) {
|
||||
treeString.append(QUOTE)
|
||||
.append(el)
|
||||
.append(QUOTE + COMMA);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNodeBottomHalf(JsonNodeType nodeType) {
|
||||
|
||||
switch (nodeType) {
|
||||
case OBJECT:
|
||||
removeCommaIfExist();
|
||||
treeString.append(RIGHT_BRACE + COMMA);
|
||||
break;
|
||||
|
||||
case ARRAY:
|
||||
removeCommaIfExist();
|
||||
treeString.append(RIGHT_BRACKET + COMMA);
|
||||
break;
|
||||
|
||||
case BINARY:
|
||||
case BOOLEAN:
|
||||
case MISSING:
|
||||
case NULL:
|
||||
case NUMBER:
|
||||
case POJO:
|
||||
case STRING:
|
||||
log.debug("Unimplemented node type {}", nodeType);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JsonParseException("Unsupported json node type " +
|
||||
nodeType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTreeString() {
|
||||
removeCommaIfExist();
|
||||
return treeString.append(RIGHT_BRACE).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectNode getTreeNode() {
|
||||
ObjectNode node = null;
|
||||
try {
|
||||
node = (ObjectNode) (new ObjectMapper()).readTree(getTreeString());
|
||||
} catch (IOException e) {
|
||||
log.error("Parse json string failed {}", e.getMessage());
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
private void appendField(String fieldName) {
|
||||
if (!isNullOrEmpty(fieldName)) {
|
||||
treeString.append(QUOTE)
|
||||
.append(fieldName)
|
||||
.append(QUOTE + COLON);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCommaIfExist() {
|
||||
int lastIndex = treeString.length() - 1;
|
||||
if (treeString.charAt(lastIndex) == COMMA.charAt(0)) {
|
||||
treeString.deleteCharAt(lastIndex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonWalker;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonListener;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents implementation of JSON walk, which walks the JSON object node.
|
||||
*/
|
||||
public class DefaultJsonWalker implements JsonWalker {
|
||||
@Override
|
||||
public void walk(JsonListener jsonListener, String fieldName,
|
||||
ObjectNode objectNode) {
|
||||
|
||||
//enter the object node, the original ObjectNode should have a module
|
||||
//name as fieldName.
|
||||
jsonListener.enterJsonNode(fieldName, objectNode);
|
||||
//the node has no children, then exist and return.
|
||||
if (!objectNode.isContainerNode()) {
|
||||
jsonListener.exitJsonNode(objectNode);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields();
|
||||
while (fields.hasNext()) {
|
||||
//get the children entry of the node
|
||||
Map.Entry<String, JsonNode> currentChild = fields.next();
|
||||
String key = currentChild.getKey();
|
||||
JsonNode value = currentChild.getValue();
|
||||
//if the entry's value has its own children, do a recursion.
|
||||
//if the entry has no children, store the key and value.
|
||||
//for we don't know the specific type of the entry's value, we
|
||||
// should give it to a method which can handle JsonNode
|
||||
if (value.isContainerNode()) {
|
||||
walkJsonNode(jsonListener, key, value);
|
||||
} else {
|
||||
jsonListener.enterJsonNode(key, value);
|
||||
jsonListener.exitJsonNode(value);
|
||||
}
|
||||
}
|
||||
jsonListener.exitJsonNode(objectNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks the JSON data tree. This method is called when we don't know
|
||||
* the exact type of a json node.
|
||||
*
|
||||
* @param jsonListener Json listener implemented by the user
|
||||
* @param fieldName the original object node field
|
||||
* @param rootNode the json node which needs to be walk
|
||||
*/
|
||||
private void walkJsonNode(JsonListener jsonListener, String fieldName,
|
||||
JsonNode rootNode) {
|
||||
if (rootNode.isObject()) {
|
||||
walk(jsonListener, fieldName, (ObjectNode) rootNode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootNode.isArray()) {
|
||||
walkArrayNode(jsonListener, fieldName, (ArrayNode) rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks the JSON data tree. This method is called when the user knows the
|
||||
* json node type is ArrayNode.
|
||||
*
|
||||
* @param jsonListener Json listener implemented by the user
|
||||
* @param fieldName the original object node field
|
||||
* @param rootNode the json node which needs to be walk
|
||||
*/
|
||||
private void walkArrayNode(JsonListener jsonListener, String fieldName,
|
||||
ArrayNode rootNode) {
|
||||
if (rootNode == null) {
|
||||
return;
|
||||
}
|
||||
//enter the array node.
|
||||
jsonListener.enterJsonNode(fieldName, rootNode);
|
||||
Iterator<JsonNode> children = rootNode.elements();
|
||||
while (children.hasNext()) {
|
||||
JsonNode currentChild = children.next();
|
||||
if (currentChild.isContainerNode()) {
|
||||
walkJsonNode(jsonListener, null, currentChild);
|
||||
}
|
||||
}
|
||||
jsonListener.exitJsonNode(rootNode);
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import org.onosproject.protocol.restconf.server.utils.exceptions.JsonParseException;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonListener;
|
||||
import org.onosproject.yms.ydt.YdtBuilder;
|
||||
import org.onosproject.yms.ydt.YdtContext;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.fasterxml.jackson.databind.node.JsonNodeType.ARRAY;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static org.onosproject.yms.ydt.YdtType.MULTI_INSTANCE_NODE;
|
||||
import static org.onosproject.yms.ydt.YdtType.SINGLE_INSTANCE_NODE;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Represents default implementation of codec JSON listener.
|
||||
*/
|
||||
public class JsonToYdtListener implements JsonListener {
|
||||
|
||||
private static final String INPUT_FIELD_NAME = "input";
|
||||
private static final String COLON = ":";
|
||||
private static final int INPUT_FIELD_LENGTH = 2;
|
||||
private static final String E_UNSUP_TYPE = "Unsupported node type %s " +
|
||||
"field name is %s fieldName";
|
||||
|
||||
private Logger log = getLogger(getClass());
|
||||
|
||||
private YdtBuilder ydtBuilder;
|
||||
private String defaultMultiInsNodeName;
|
||||
private YdtContext rpcModule;
|
||||
|
||||
/**
|
||||
* Creates a listener for the process of a Json Object, the listener will
|
||||
* try to transfer the JSON object to a Ydt builder.
|
||||
*
|
||||
* @param ydtBuilder the YDT builder to build a YDT object
|
||||
*/
|
||||
public JsonToYdtListener(YdtBuilder ydtBuilder) {
|
||||
this.ydtBuilder = ydtBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterJsonNode(String fieldName, JsonNode node) {
|
||||
if (isNullOrEmpty(fieldName)) {
|
||||
if (!isNullOrEmpty(defaultMultiInsNodeName)) {
|
||||
ydtBuilder.addChild(defaultMultiInsNodeName, null,
|
||||
MULTI_INSTANCE_NODE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
JsonNodeType nodeType = node.getNodeType();
|
||||
switch (nodeType) {
|
||||
case OBJECT:
|
||||
processObjectNode(fieldName);
|
||||
break;
|
||||
|
||||
case ARRAY:
|
||||
processArrayNode(fieldName, node);
|
||||
break;
|
||||
|
||||
//TODO for now, just process the following three node type
|
||||
case STRING:
|
||||
case NUMBER:
|
||||
case BOOLEAN:
|
||||
ydtBuilder.addLeaf(fieldName, null, node.asText());
|
||||
break;
|
||||
|
||||
case BINARY:
|
||||
case MISSING:
|
||||
case NULL:
|
||||
case POJO:
|
||||
log.debug("Unimplemented node type {}", nodeType);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JsonParseException(String.format(E_UNSUP_TYPE,
|
||||
nodeType, fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitJsonNode(JsonNode jsonNode) {
|
||||
if (jsonNode.getNodeType() == ARRAY) {
|
||||
return;
|
||||
}
|
||||
ydtBuilder.traverseToParent();
|
||||
YdtContext curNode = ydtBuilder.getCurNode();
|
||||
//if the current node is the RPC node, then should go to the father
|
||||
//for we have enter the RPC node and Input node at the same time
|
||||
//and the input is the only child of RPC node.
|
||||
|
||||
if (curNode == null) {
|
||||
return;
|
||||
}
|
||||
String name = curNode.getName();
|
||||
if (rpcModule != null && name.equals(rpcModule.getName())) {
|
||||
ydtBuilder.traverseToParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void processObjectNode(String fieldName) {
|
||||
String[] segments = fieldName.split(COLON);
|
||||
Boolean isLastInput = segments.length == INPUT_FIELD_LENGTH &&
|
||||
segments[INPUT_FIELD_LENGTH - 1].equals(INPUT_FIELD_NAME);
|
||||
int first = 0;
|
||||
int second = 1;
|
||||
if (isLastInput) {
|
||||
ydtBuilder.addChild(segments[first], null, SINGLE_INSTANCE_NODE);
|
||||
rpcModule = ydtBuilder.getCurNode();
|
||||
ydtBuilder.addChild(segments[second], null, SINGLE_INSTANCE_NODE);
|
||||
} else {
|
||||
ydtBuilder.addChild(fieldName, null, SINGLE_INSTANCE_NODE);
|
||||
}
|
||||
}
|
||||
|
||||
private void processArrayNode(String fieldName, JsonNode node) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
Set<String> sets = new HashSet<>();
|
||||
Iterator<JsonNode> elements = arrayNode.elements();
|
||||
boolean isLeafList = true;
|
||||
while (elements.hasNext()) {
|
||||
JsonNode element = elements.next();
|
||||
JsonNodeType eleType = element.getNodeType();
|
||||
|
||||
if (eleType == JsonNodeType.STRING ||
|
||||
eleType == JsonNodeType.NUMBER ||
|
||||
eleType == JsonNodeType.BOOLEAN) {
|
||||
sets.add(element.asText());
|
||||
} else {
|
||||
isLeafList = false;
|
||||
}
|
||||
}
|
||||
if (isLeafList) {
|
||||
//leaf-list
|
||||
ydtBuilder.addLeaf(fieldName, null, sets);
|
||||
} else {
|
||||
this.defaultMultiInsNodeName = fieldName;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.onosproject.protocol.restconf.server.utils.exceptions.JsonParseException;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonBuilder;
|
||||
import org.onosproject.yms.ydt.YdtBuilder;
|
||||
import org.onosproject.yms.ydt.YdtContext;
|
||||
import org.onosproject.yms.ydt.YdtContextOperationType;
|
||||
import org.onosproject.yms.ydt.YdtListener;
|
||||
import org.onosproject.yms.ydt.YdtType;
|
||||
import org.onosproject.yms.ydt.YdtWalker;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.onosproject.yms.ydt.YdtContextOperationType.NONE;
|
||||
|
||||
/**
|
||||
* Utils to complete the conversion between JSON and YDT(YANG DATA MODEL).
|
||||
*/
|
||||
public final class ParserUtils {
|
||||
|
||||
private static final Splitter SLASH_SPLITTER = Splitter.on('/');
|
||||
private static final Splitter COMMA_SPLITTER = Splitter.on(',');
|
||||
private static final String EQUAL = "=";
|
||||
private static final String COMMA = ",";
|
||||
private static final String COLON = ":";
|
||||
private static final String URI_ENCODING_CHAR_SET = "ISO-8859-1";
|
||||
private static final String ERROR_LIST_MSG = "List/Leaf-list node should be " +
|
||||
"in format \"nodeName=key\"or \"nodeName=instance-value\"";
|
||||
private static final String ERROR_MODULE_MSG = "First node should be in " +
|
||||
"format \"moduleName:nodeName\"";
|
||||
|
||||
// no instantiation
|
||||
private ParserUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts URI identifier to YDT builder.
|
||||
*
|
||||
* @param id the uri identifier from web request
|
||||
* @param builder the base ydt builder
|
||||
* @param opType the ydt operation type for the uri
|
||||
*/
|
||||
public static void convertUriToYdt(String id,
|
||||
YdtBuilder builder,
|
||||
YdtContextOperationType opType) {
|
||||
checkNotNull(id, "uri identifier should not be null");
|
||||
List<String> paths = urlPathArgsDecode(SLASH_SPLITTER.split(id));
|
||||
if (!paths.isEmpty()) {
|
||||
processPathSegments(paths, builder, opType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts JSON objectNode to YDT builder. The objectNode can be any
|
||||
* standard JSON node, node just for RESTconf payload.
|
||||
*
|
||||
* @param objectNode the objectNode from web request
|
||||
* @param builder the base ydt builder
|
||||
*/
|
||||
public static void convertJsonToYdt(ObjectNode objectNode,
|
||||
YdtBuilder builder) {
|
||||
|
||||
JsonToYdtListener listener = new JsonToYdtListener(builder);
|
||||
new DefaultJsonWalker().walk(listener, null, objectNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Ydt context tree to a JSON object.
|
||||
*
|
||||
* @param rootName the name of the YdtContext from which the YdtListener
|
||||
* start to builder a Json Object
|
||||
* @param context a abstract data model for YANG data
|
||||
* @param walker abstraction of an entity which provides interfaces for
|
||||
* YDT walk
|
||||
* @return the JSON node corresponding the YANG data
|
||||
*/
|
||||
public static ObjectNode convertYdtToJson(String rootName,
|
||||
YdtContext context,
|
||||
YdtWalker walker) {
|
||||
JsonBuilder builder = new DefaultJsonBuilder();
|
||||
YdtListener listener = new YdtToJsonListener(rootName, builder);
|
||||
walker.walk(listener, context);
|
||||
return builder.getTreeNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of path segments to a YDT builder tree.
|
||||
*
|
||||
* @param paths the list of path segments split from URI
|
||||
* @param builder the base YDT builder
|
||||
* @param opType the YDT operation type for the Path segment
|
||||
* @return the YDT builder with the tree info of paths
|
||||
*/
|
||||
private static YdtBuilder processPathSegments(List<String> paths,
|
||||
YdtBuilder builder,
|
||||
YdtContextOperationType opType) {
|
||||
if (paths.isEmpty()) {
|
||||
return builder;
|
||||
}
|
||||
boolean isLastNode = paths.size() == 1;
|
||||
YdtContextOperationType opTypeForThisNode = isLastNode ? opType : NONE;
|
||||
|
||||
String path = paths.iterator().next();
|
||||
if (path.contains(COLON)) {
|
||||
addModule(builder, path);
|
||||
addNode(path, builder, opTypeForThisNode);
|
||||
} else if (path.contains(EQUAL)) {
|
||||
addListOrLeafList(path, builder, opTypeForThisNode);
|
||||
} else {
|
||||
addLeaf(path, builder, opTypeForThisNode);
|
||||
}
|
||||
|
||||
if (isLastNode) {
|
||||
return builder;
|
||||
}
|
||||
List<String> remainPaths = paths.subList(1, paths.size());
|
||||
processPathSegments(remainPaths, builder, opType);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static YdtBuilder addModule(YdtBuilder builder, String path) {
|
||||
String moduleName = getPreSegment(path, COLON);
|
||||
if (moduleName == null) {
|
||||
throw new JsonParseException(ERROR_MODULE_MSG);
|
||||
}
|
||||
builder.addChild(moduleName, null, YdtType.SINGLE_INSTANCE_NODE);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static YdtBuilder addNode(String path, YdtBuilder builder,
|
||||
YdtContextOperationType opType) {
|
||||
String nodeName = getLatterSegment(path, COLON);
|
||||
builder.addChild(nodeName,
|
||||
null,
|
||||
YdtType.SINGLE_INSTANCE_NODE,
|
||||
opType);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static YdtBuilder addListOrLeafList(String path,
|
||||
YdtBuilder builder,
|
||||
YdtContextOperationType opType) {
|
||||
String nodeName = getPreSegment(path, EQUAL);
|
||||
String keyStr = getLatterSegment(path, EQUAL);
|
||||
if (keyStr == null) {
|
||||
throw new JsonParseException(ERROR_LIST_MSG);
|
||||
}
|
||||
builder.setDefaultEditOperationType(opType);
|
||||
if (keyStr.contains(COMMA)) {
|
||||
List<String> keys = Lists.
|
||||
newArrayList(COMMA_SPLITTER.split(keyStr));
|
||||
builder.addMultiInstanceChild(nodeName, null, keys);
|
||||
} else {
|
||||
builder.addMultiInstanceChild(nodeName, null,
|
||||
Lists.newArrayList(keyStr));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static YdtBuilder addLeaf(String path, YdtBuilder builder,
|
||||
YdtContextOperationType opType) {
|
||||
checkNotNull(path);
|
||||
builder.addChild(path, null, opType);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous segment of a path which is separated by a split char.
|
||||
* For example:
|
||||
* <pre>
|
||||
* "foo:bar", ":" --> "foo"
|
||||
* </pre>
|
||||
*
|
||||
* @param path the original path string
|
||||
* @param splitChar char used to split the path
|
||||
* @return the previous segment of the path
|
||||
*/
|
||||
private static String getPreSegment(String path, String splitChar) {
|
||||
int idx = path.indexOf(splitChar);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (path.indexOf(splitChar, idx + 1) != -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path.substring(0, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latter segment of a path which is separated by a split char.
|
||||
* For example:
|
||||
* <pre>
|
||||
* "foo:bar", ":" --> "bar"
|
||||
* </pre>
|
||||
*
|
||||
* @param path the original path string
|
||||
* @param splitChar char used to split the path
|
||||
* @return the latter segment of the path
|
||||
*/
|
||||
private static String getLatterSegment(String path, String splitChar) {
|
||||
int idx = path.indexOf(splitChar);
|
||||
if (idx == -1) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.indexOf(splitChar, idx + 1) != -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path.substring(idx + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of path from the original format to ISO-8859-1 code.
|
||||
*
|
||||
* @param paths the original paths
|
||||
* @return list of decoded paths
|
||||
*/
|
||||
public static List<String> urlPathArgsDecode(Iterable<String> paths) {
|
||||
try {
|
||||
List<String> decodedPathArgs = new ArrayList<>();
|
||||
for (String pathArg : paths) {
|
||||
String decode = URLDecoder.decode(pathArg,
|
||||
URI_ENCODING_CHAR_SET);
|
||||
decodedPathArgs.add(decode);
|
||||
}
|
||||
return decodedPathArgs;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new JsonParseException("Invalid URL path arg '" +
|
||||
paths + "': ", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import org.onosproject.protocol.restconf.server.utils.exceptions.YdtParseException;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonBuilder;
|
||||
import org.onosproject.yms.ydt.YdtContext;
|
||||
import org.onosproject.yms.ydt.YdtListener;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
/**
|
||||
* Represents implementation of codec YDT listener.
|
||||
*/
|
||||
public class YdtToJsonListener implements YdtListener {
|
||||
|
||||
private static final String EMPTY = "";
|
||||
private JsonBuilder jsonBuilder;
|
||||
//the root name of the json
|
||||
//the input YdtContext is usually a total tree of a YANG resource
|
||||
//this property is used to mark the start of the request node.
|
||||
private String rootName;
|
||||
//the parse state
|
||||
private boolean isBegin;
|
||||
|
||||
/**
|
||||
* Creates a listener for the process of a Ydt, the listener will try to
|
||||
* transfer the Ydt to a JSON Object.
|
||||
*
|
||||
* @param rootName the name of a specific YdtContext begin to process
|
||||
* @param jsonBuilder the JSON builder to build a JSON object
|
||||
*/
|
||||
public YdtToJsonListener(String rootName, JsonBuilder jsonBuilder) {
|
||||
this.jsonBuilder = jsonBuilder;
|
||||
this.rootName = rootName;
|
||||
this.isBegin = isNullOrEmpty(rootName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterYdtNode(YdtContext ydtContext) {
|
||||
String name = ydtContext.getName();
|
||||
|
||||
if (!isBegin && name.equals(rootName)) {
|
||||
isBegin = true;
|
||||
}
|
||||
if (!isBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ydtContext.getYdtType()) {
|
||||
|
||||
case SINGLE_INSTANCE_NODE:
|
||||
jsonBuilder.addNodeTopHalf(name, JsonNodeType.OBJECT);
|
||||
break;
|
||||
case MULTI_INSTANCE_NODE:
|
||||
YdtContext preNode = ydtContext.getPreviousSibling();
|
||||
if (preNode == null || !preNode.getName().equals(name)) {
|
||||
jsonBuilder.addNodeTopHalf(name, JsonNodeType.ARRAY);
|
||||
}
|
||||
jsonBuilder.addNodeTopHalf(EMPTY, JsonNodeType.OBJECT);
|
||||
break;
|
||||
case SINGLE_INSTANCE_LEAF_VALUE_NODE:
|
||||
jsonBuilder.addNodeWithValueTopHalf(name, ydtContext.getValue());
|
||||
break;
|
||||
case MULTI_INSTANCE_LEAF_VALUE_NODE:
|
||||
jsonBuilder.addNodeWithSetTopHalf(name, ydtContext.getValueSet());
|
||||
break;
|
||||
default:
|
||||
throw new YdtParseException("unknown Ydt type " +
|
||||
ydtContext.getYdtType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitYdtNode(YdtContext ydtContext) {
|
||||
|
||||
if (!isBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
String curName = ydtContext.getName();
|
||||
YdtContext nextNode = ydtContext.getNextSibling();
|
||||
switch (ydtContext.getYdtType()) {
|
||||
|
||||
case SINGLE_INSTANCE_NODE:
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.OBJECT);
|
||||
break;
|
||||
case MULTI_INSTANCE_NODE:
|
||||
if (nextNode == null || !nextNode.getName().equals(curName)) {
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.OBJECT);
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.ARRAY);
|
||||
} else {
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.OBJECT);
|
||||
}
|
||||
break;
|
||||
case SINGLE_INSTANCE_LEAF_VALUE_NODE:
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.STRING);
|
||||
break;
|
||||
case MULTI_INSTANCE_LEAF_VALUE_NODE:
|
||||
jsonBuilder.addNodeBottomHalf(JsonNodeType.ARRAY);
|
||||
break;
|
||||
default:
|
||||
throw new YdtParseException("Unknown Ydt type " +
|
||||
ydtContext.getYdtType());
|
||||
}
|
||||
if (curName.equals(rootName) &&
|
||||
(nextNode == null || !nextNode.getName().equals(rootName))) {
|
||||
isBegin = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provider utilities to support the data format based encoding and decoding,
|
||||
* used by YMSC to operate on different data format and YDT(YANG DATA TYPE).
|
||||
*/
|
||||
|
||||
package org.onosproject.protocol.restconf.server.utils.parser.json;
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onosproject.restconf.utils.parser.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.api.JsonListener;
|
||||
import org.onosproject.protocol.restconf.server.utils.parser.json.DefaultJsonWalker;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
public class DefaultJsonWalkerTest {
|
||||
|
||||
private static final String[] EXP_TEXT_ARRAY = {
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is name",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is STRING name is surname",
|
||||
"Exit: type is STRING",
|
||||
"Exit: type is OBJECT"
|
||||
};
|
||||
|
||||
private static final String[] EXP_NODE_LIST_ARRAY = {
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is surname",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is ARRAY name is networklist",
|
||||
"Exit: type is ARRAY",
|
||||
"Exit: type is OBJECT"
|
||||
};
|
||||
|
||||
private static final String[] EXP_NODE_WITH_ARRAY = {
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is surname",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is ARRAY name is networklist",
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is network-id",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is STRING name is server-provided",
|
||||
"Exit: type is STRING",
|
||||
"Exit: type is OBJECT",
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is network-id",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is STRING name is server-provided",
|
||||
"Exit: type is STRING",
|
||||
"Exit: type is OBJECT",
|
||||
"Enter: type is OBJECT name is null",
|
||||
"Enter: type is STRING name is network-id",
|
||||
"Exit: type is STRING",
|
||||
"Enter: type is STRING name is server-provided",
|
||||
"Exit: type is STRING",
|
||||
"Exit: type is OBJECT",
|
||||
"Exit: type is ARRAY",
|
||||
"Exit: type is OBJECT"
|
||||
};
|
||||
private static final String WRONG_CONTENT_MSG = "Wrong content in array";
|
||||
|
||||
private final List<String> logger = new ArrayList<>();
|
||||
|
||||
DefaultJsonWalker defaultJsonWalker = new DefaultJsonWalker();
|
||||
InternalJsonListener jsonListener = new InternalJsonListener();
|
||||
|
||||
ObjectNode arrayNode;
|
||||
ObjectNode textNode;
|
||||
ObjectNode listNode;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
textNode = loadJsonFile("/textNode.json");
|
||||
listNode = loadJsonFile("/listNode.json");
|
||||
arrayNode = loadJsonFile("/arrayNode.json");
|
||||
}
|
||||
|
||||
private ObjectNode loadJsonFile(String path) throws Exception {
|
||||
InputStream jsonStream = getClass().getResourceAsStream(path);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return (ObjectNode) mapper.readTree(jsonStream);
|
||||
}
|
||||
|
||||
private void assertWalkResult(String[] expectArray, List<String> logger) {
|
||||
for (int i = 0; i < expectArray.length; i++) {
|
||||
assertThat(WRONG_CONTENT_MSG,
|
||||
true,
|
||||
is(logger.get(i).contentEquals(expectArray[i])));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWalkTextNode() throws Exception {
|
||||
|
||||
defaultJsonWalker.walk(jsonListener, null, textNode);
|
||||
assertWalkResult(EXP_TEXT_ARRAY, logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWalkNodeWithList() throws Exception {
|
||||
defaultJsonWalker.walk(jsonListener, null, listNode);
|
||||
assertWalkResult(EXP_NODE_LIST_ARRAY, logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWalkNodeWithArray() throws Exception {
|
||||
defaultJsonWalker.walk(jsonListener, null, arrayNode);
|
||||
assertWalkResult(EXP_NODE_WITH_ARRAY, logger);
|
||||
}
|
||||
|
||||
private class InternalJsonListener implements JsonListener {
|
||||
|
||||
@Override
|
||||
public void enterJsonNode(String fieldName, JsonNode node) {
|
||||
logger.add("Enter: type is " + node.getNodeType() + " name is " +
|
||||
fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitJsonNode(JsonNode node) {
|
||||
logger.add("Exit: type is " + node.getNodeType());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"surname": "Bangalore",
|
||||
"networklist":[
|
||||
{
|
||||
"network-id": "520",
|
||||
"server-provided": "123"
|
||||
},
|
||||
{
|
||||
"network-id": "521",
|
||||
"server-provided": "124"
|
||||
},
|
||||
{
|
||||
"network-id": "523",
|
||||
"server-provided": "125"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"surname": "Bangalore",
|
||||
"networklist":[
|
||||
"0.79",
|
||||
"1.04",
|
||||
"3.14"
|
||||
]
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Huawei",
|
||||
"surname": "Bangalore"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user