diff --git a/protocols/restconf/server/BUCK b/protocols/restconf/server/BUCK index 8b4f0d2ecc..c1383b6b9a 100644 --- a/protocols/restconf/server/BUCK +++ b/protocols/restconf/server/BUCK @@ -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 ( diff --git a/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/Patch.java b/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/Patch.java new file mode 100644 index 0000000000..5004944831 --- /dev/null +++ b/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/Patch.java @@ -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 { +} \ No newline at end of file diff --git a/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/RestconfService.java b/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/RestconfService.java index 6fea21d71e..053356b070 100644 --- a/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/RestconfService.java +++ b/protocols/restconf/server/api/src/main/java/org/onosproject/protocol/restconf/server/api/RestconfService.java @@ -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. + *

+ * 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 output) throws RestconfException; + void subscribeEventStream(String streamId, ChunkedOutput output) + throws RestconfException; } diff --git a/protocols/restconf/server/app/app.xml b/protocols/restconf/server/app/app.xml index 1e8723ed8d..28775b6b7c 100644 --- a/protocols/restconf/server/app/app.xml +++ b/protocols/restconf/server/app/app.xml @@ -22,4 +22,5 @@ mvn:${project.groupId}/onos-restconf-server-api/${project.version} mvn:${project.groupId}/onos-restconf-server-restconfmanager/${project.version} mvn:${project.groupId}/onos-restconf-server-rpp/${project.version} + mvn:${project.groupId}/onos-restconf-server-utils/${project.version} diff --git a/protocols/restconf/server/app/features.xml b/protocols/restconf/server/app/features.xml index b7bcce4873..2ef47070cd 100644 --- a/protocols/restconf/server/app/features.xml +++ b/protocols/restconf/server/app/features.xml @@ -21,5 +21,6 @@ mvn:${project.groupId}/onos-restconf-server-api/${project.version} mvn:${project.groupId}/onos-restconf-server-restconfmanager/${project.version} mvn:${project.groupId}/onos-restconf-server-rpp/${project.version} + mvn:${project.groupId}/onos-restconf-server-utils/${project.version} diff --git a/protocols/restconf/server/app/pom.xml b/protocols/restconf/server/app/pom.xml index 0875b99739..4f20be1e2f 100644 --- a/protocols/restconf/server/app/pom.xml +++ b/protocols/restconf/server/app/pom.xml @@ -51,5 +51,10 @@ onos-restconf-server-rpp ${project.version} + + org.onosproject + onos-restconf-server-utils + ${project.version} + diff --git a/protocols/restconf/server/pom.xml b/protocols/restconf/server/pom.xml index 6ef2700a86..9874b45ea7 100644 --- a/protocols/restconf/server/pom.xml +++ b/protocols/restconf/server/pom.xml @@ -34,6 +34,7 @@ restconfmgr rpp app + utils RESTCONF Server Module diff --git a/protocols/restconf/server/restconfmgr/BUCK b/protocols/restconf/server/restconfmgr/BUCK index 100f922969..26d807609e 100644 --- a/protocols/restconf/server/restconfmgr/BUCK +++ b/protocols/restconf/server/restconfmgr/BUCK @@ -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 ( diff --git a/protocols/restconf/server/restconfmgr/pom.xml b/protocols/restconf/server/restconfmgr/pom.xml index 4c8b8c5d01..3fb62e357a 100644 --- a/protocols/restconf/server/restconfmgr/pom.xml +++ b/protocols/restconf/server/restconfmgr/pom.xml @@ -58,6 +58,16 @@ org.apache.felix org.apache.felix.scr.annotations + + org.onosproject + onos-restconf-server-utils + ${project.version} + + + org.onosproject + onos-app-yms-api + ${project.version} + diff --git a/protocols/restconf/server/restconfmgr/src/main/java/org/onosproject/protocol/restconf/server/restconfmanager/RestconfManager.java b/protocols/restconf/server/restconfmgr/src/main/java/org/onosproject/protocol/restconf/server/restconfmanager/RestconfManager.java index ee8506f7b7..5237f50417 100644 --- a/protocols/restconf/server/restconfmgr/src/main/java/org/onosproject/protocol/restconf/server/restconfmanager/RestconfManager.java +++ b/protocols/restconf/server/restconfmgr/src/main/java/org/onosproject/protocol/restconf/server/restconfmanager/RestconfManager.java @@ -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> eventQueueList = - new ConcurrentHashMap>(); + 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 output) throws RestconfException { - BlockingQueue eventQueue = new LinkedBlockingQueue(); + public void subscribeEventStream(String streamId, + ChunkedOutput output) + throws RestconfException { + BlockingQueue 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 output; private final BlockingQueue bqueue; - public EventConsumer(ChunkedOutput output, BlockingQueue q) { + public EventConsumer(ChunkedOutput output, + BlockingQueue 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 diff --git a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java index f2608a32bb..35abdb026e 100644 --- a/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java +++ b/protocols/restconf/server/rpp/src/main/java/org/onosproject/protocol/restconf/server/rpp/RestconfWebResource.java @@ -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(); + } + } + } diff --git a/protocols/restconf/server/utils/BUCK b/protocols/restconf/server/utils/BUCK new file mode 100644 index 0000000000..d1410c0cd2 --- /dev/null +++ b/protocols/restconf/server/utils/BUCK @@ -0,0 +1,8 @@ +COMPILE_DEPS = [ + '//lib:CORE_DEPS', + '//apps/yms/api:onos-apps-yms-api', +] + +osgi_jar_with_tests ( + deps = COMPILE_DEPS, +) diff --git a/protocols/restconf/server/utils/pom.xml b/protocols/restconf/server/utils/pom.xml new file mode 100644 index 0000000000..943be73a5e --- /dev/null +++ b/protocols/restconf/server/utils/pom.xml @@ -0,0 +1,27 @@ + + + + onos-restconf-server + org.onosproject + 1.8.0-SNAPSHOT + + 4.0.0 + + onos-restconf-server-utils + bundle + + + org.onosproject + onos-app-yms-api + ${project.version} + + + org.easymock + easymock + test + + + + \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/JsonParseException.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/JsonParseException.java new file mode 100644 index 0000000000..cd178c58bd --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/JsonParseException.java @@ -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); + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/YdtParseException.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/YdtParseException.java new file mode 100644 index 0000000000..c2e121b47e --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/YdtParseException.java @@ -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); + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/package-info.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/package-info.java new file mode 100644 index 0000000000..508bca0134 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/exceptions/package-info.java @@ -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; \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonBuilder.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonBuilder.java new file mode 100644 index 0000000000..f56422f0d5 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonBuilder.java @@ -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 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(); +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonListener.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonListener.java new file mode 100644 index 0000000000..7432dd3e7e --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonListener.java @@ -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); + +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonWalker.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonWalker.java new file mode 100644 index 0000000000..59ce2aba85 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/JsonWalker.java @@ -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); +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/package-info.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/package-info.java new file mode 100644 index 0000000000..51d28df540 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/api/package-info.java @@ -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; \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonBuilder.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonBuilder.java new file mode 100644 index 0000000000..b5f2911953 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonBuilder.java @@ -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 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); + } + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonWalker.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonWalker.java new file mode 100644 index 0000000000..c1802acddf --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/DefaultJsonWalker.java @@ -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> fields = objectNode.fields(); + while (fields.hasNext()) { + //get the children entry of the node + Map.Entry 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 children = rootNode.elements(); + while (children.hasNext()) { + JsonNode currentChild = children.next(); + if (currentChild.isContainerNode()) { + walkJsonNode(jsonListener, null, currentChild); + } + } + jsonListener.exitJsonNode(rootNode); + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/JsonToYdtListener.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/JsonToYdtListener.java new file mode 100644 index 0000000000..4a130f7a28 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/JsonToYdtListener.java @@ -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 sets = new HashSet<>(); + Iterator 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; + } + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/ParserUtils.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/ParserUtils.java new file mode 100644 index 0000000000..fa8b133faf --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/ParserUtils.java @@ -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 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 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 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 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: + *

+     * "foo:bar", ":"   -->  "foo"
+     * 
+ * + * @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: + *
+     * "foo:bar", ":"   -->  "bar"
+     * 
+ * + * @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 urlPathArgsDecode(Iterable paths) { + try { + List 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); + } + } +} diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/YdtToJsonListener.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/YdtToJsonListener.java new file mode 100644 index 0000000000..b0ce26b5d9 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/YdtToJsonListener.java @@ -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; + } + } +} \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/package-info.java b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/package-info.java new file mode 100644 index 0000000000..3e3c49b809 --- /dev/null +++ b/protocols/restconf/server/utils/src/main/java/org/onosproject/protocol/restconf/server/utils/parser/json/package-info.java @@ -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; \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/test/java/org/onosproject/restconf/utils/parser/json/DefaultJsonWalkerTest.java b/protocols/restconf/server/utils/src/test/java/org/onosproject/restconf/utils/parser/json/DefaultJsonWalkerTest.java new file mode 100644 index 0000000000..c82f0f8d8c --- /dev/null +++ b/protocols/restconf/server/utils/src/test/java/org/onosproject/restconf/utils/parser/json/DefaultJsonWalkerTest.java @@ -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 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 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()); + } + } +} \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/test/resources/arrayNode.json b/protocols/restconf/server/utils/src/test/resources/arrayNode.json new file mode 100644 index 0000000000..2035e82232 --- /dev/null +++ b/protocols/restconf/server/utils/src/test/resources/arrayNode.json @@ -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" + } + + ] +} \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/test/resources/listNode.json b/protocols/restconf/server/utils/src/test/resources/listNode.json new file mode 100644 index 0000000000..c7902f5c27 --- /dev/null +++ b/protocols/restconf/server/utils/src/test/resources/listNode.json @@ -0,0 +1,8 @@ +{ + "surname": "Bangalore", + "networklist":[ + "0.79", + "1.04", + "3.14" + ] +} \ No newline at end of file diff --git a/protocols/restconf/server/utils/src/test/resources/textNode.json b/protocols/restconf/server/utils/src/test/resources/textNode.json new file mode 100644 index 0000000000..8894cfbf23 --- /dev/null +++ b/protocols/restconf/server/utils/src/test/resources/textNode.json @@ -0,0 +1,4 @@ +{ + "name": "Huawei", + "surname": "Bangalore" +} \ No newline at end of file