Bug 3866: Support for Restconf HTTP Patch
[netconf.git] / opendaylight / restconf / sal-rest-connector / src / main / java / org / opendaylight / netconf / sal / restconf / impl / RestconfImpl.java
index a0f6cb31d4534c2a5ebe44ff8f254da8e761cf85..fedcd6bb6ae4f7bae85e86a7a8c6474f13b42678 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2014, 2015 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
@@ -8,6 +8,7 @@
 
 package org.opendaylight.netconf.sal.restconf.impl;
 
+import com.google.common.base.CharMatcher;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
@@ -35,18 +36,19 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
-import org.apache.commons.lang3.StringUtils;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
@@ -96,7 +98,7 @@ import org.opendaylight.yangtools.yang.parser.builder.api.GroupingBuilder;
 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder;
 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder;
 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
-import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -108,8 +110,6 @@ public class RestconfImpl implements RestconfService {
 
     private static final int CHAR_NOT_FOUND = -1;
 
-    private static final String MOUNT_POINT_MODULE_NAME = "ietf-netconf";
-
     private static final SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
 
     private static final String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
@@ -371,10 +371,7 @@ public class RestconfImpl implements RestconfService {
         }
 
         final Set<Module> fakeRpcModules = Collections.singleton(restConfModuleBuilder.build());
-
-        final YangParserImpl yangParser = new YangParserImpl();
-        final SchemaContext fakeSchemaCx = yangParser.resolveSchemaContext(fakeRpcModules);
-
+        final SchemaContext fakeSchemaCx = EffectiveSchemaContext.resolveSchemaContext(fakeRpcModules);
         final InstanceIdentifierContext<?> fakeIICx = new InstanceIdentifierContext<>(null, operContainerSchemaNode, mountPoint, fakeSchemaCx);
 
         return new NormalizedNodeContext(fakeIICx, operContainerNode.build());
@@ -391,7 +388,7 @@ public class RestconfImpl implements RestconfService {
         return restconfModule;
     }
 
-    private QName getModuleNameAndRevision(final String identifier) {
+    private static QName getModuleNameAndRevision(final String identifier) {
         final int mountIndex = identifier.indexOf(ControllerContext.MOUNT);
         String moduleNameAndRevision = "";
         if (mountIndex >= 0) {
@@ -434,7 +431,7 @@ public class RestconfImpl implements RestconfService {
         final CheckedFuture<DOMRpcResult, DOMRpcException> response;
         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
         final SchemaContext schemaContext;
-        if (identifier.contains(MOUNT_POINT_MODULE_NAME) && mountPoint != null) {
+        if (mountPoint != null) {
             final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
             if ( ! mountRpcServices.isPresent()) {
                 LOG.debug("Error: Rpc service is missing.");
@@ -464,7 +461,7 @@ public class RestconfImpl implements RestconfService {
                 QueryParametersParser.parseWriterParameters(uriInfo));
     }
 
-    private DOMRpcResult checkRpcResponse(final CheckedFuture<DOMRpcResult, DOMRpcException> response) {
+    private static DOMRpcResult checkRpcResponse(final CheckedFuture<DOMRpcResult, DOMRpcException> response) {
         if (response == null) {
             return null;
         }
@@ -490,6 +487,8 @@ public class RestconfImpl implements RestconfService {
                 if (cause instanceof IllegalArgumentException) {
                     throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL,
                             ErrorTag.INVALID_VALUE);
+                } else if (cause instanceof DOMRpcImplementationNotAvailableException) {
+                    throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
                 }
                 throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",cause);
             } else {
@@ -502,7 +501,7 @@ public class RestconfImpl implements RestconfService {
         }
     }
 
-    private void validateInput(final SchemaNode inputSchema, final NormalizedNodeContext payload) {
+    private static void validateInput(final SchemaNode inputSchema, final NormalizedNodeContext payload) {
         if (inputSchema != null && payload.getData() == null) {
             // expected a non null payload
             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
@@ -569,7 +568,7 @@ public class RestconfImpl implements RestconfService {
 
     @Override
     public NormalizedNodeContext invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
-        if (StringUtils.isNotBlank(noPayload)) {
+        if (noPayload != null && !CharMatcher.WHITESPACE.matchesAllOf(noPayload)) {
             throw new RestconfDocumentedException("Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
         }
 
@@ -600,7 +599,7 @@ public class RestconfImpl implements RestconfService {
 
         RpcDefinition rpc = null;
         if (mountPoint == null) {
-            rpc = controllerContext.getRpcDefinition(identifierDecoded);
+            rpc = controllerContext.getRpcDefinition(identifierDecoded, null);
         } else {
             rpc = findRpc(mountPoint.getSchemaContext(), identifierDecoded);
         }
@@ -633,7 +632,7 @@ public class RestconfImpl implements RestconfService {
                 result.getResult(), QueryParametersParser.parseWriterParameters(uriInfo));
     }
 
-    private RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
+    private static RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) {
         final String[] splittedIdentifier = identifierDecoded.split(":");
         if (splittedIdentifier.length != 2) {
             final String errMsg = identifierDecoded + " couldn't be splitted to 2 parts (module:rpc name)";
@@ -747,7 +746,7 @@ public class RestconfImpl implements RestconfService {
         return Response.status(Status.OK).build();
     }
 
-    private void validateTopLevelNodeName(final NormalizedNodeContext node,
+    private static void validateTopLevelNodeName(final NormalizedNodeContext node,
             final YangInstanceIdentifier identifier) {
 
         final String payloadName = node.getData().getNodeType().getLocalName();
@@ -818,7 +817,7 @@ public class RestconfImpl implements RestconfService {
     }
 
     // FIXME create RestconfIdetifierHelper and move this method there
-    private YangInstanceIdentifier checkConsistencyOfNormalizedNodeContext(final NormalizedNodeContext payload) {
+    private static YangInstanceIdentifier checkConsistencyOfNormalizedNodeContext(final NormalizedNodeContext payload) {
         Preconditions.checkArgument(payload != null);
         Preconditions.checkArgument(payload.getData() != null);
         Preconditions.checkArgument(payload.getData().getNodeType() != null);
@@ -935,8 +934,8 @@ public class RestconfImpl implements RestconfService {
      *
      * Additional parameters for subscribing to stream are loaded via rpc input parameters:
      * <ul>
-     * <li>datastore</li> - default CONFIGURATION (other values of {@link LogicalDatastoreType} enum type)
-     * <li>scope</li> - default BASE (other values of {@link DataChangeScope})
+     * <li>datastore - default CONFIGURATION (other values of {@link LogicalDatastoreType} enum type)</li>
+     * <li>scope - default BASE (other values of {@link DataChangeScope})</li>
      * </ul>
      */
     @Override
@@ -980,6 +979,22 @@ public class RestconfImpl implements RestconfService {
         return Response.status(Status.OK).location(uriToWebsocketServer).build();
     }
 
+    @Override
+    public PATCHStatusContext patchConfigurationData(String identifier, PATCHContext context, UriInfo uriInfo) {
+        if (context == null) {
+            throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+        return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
+    }
+
+    @Override
+    public PATCHStatusContext patchConfigurationData(PATCHContext context, @Context UriInfo uriInfo) {
+        if (context == null) {
+            throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+        }
+        return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
+    }
+
     /**
      * Load parameter for subscribing to stream from input composite node
      *
@@ -987,7 +1002,7 @@ public class RestconfImpl implements RestconfService {
      *            contains value
      * @return enum object if its string value is equal to {@code paramName}. In other cases null.
      */
-    private <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
+    private static <T> T parseEnumTypeParameter(final ContainerNode value, final Class<T> classDescriptor,
             final String paramName) {
         final Optional<DataContainerChild<? extends PathArgument, ?>> augNode = value.getChild(SAL_REMOTE_AUG_IDENTIFIER);
         if (!augNode.isPresent() && !(augNode instanceof AugmentationNode)) {
@@ -1012,14 +1027,14 @@ public class RestconfImpl implements RestconfService {
      * @return enum object if string value of {@code classDescriptor} enumeration is equal to {@code value}. Other cases
      *         null.
      */
-    private <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
+    private static <T> T parserURIEnumParameter(final Class<T> classDescriptor, final String value) {
         if (Strings.isNullOrEmpty(value)) {
             return null;
         }
         return resolveAsEnum(classDescriptor, value);
     }
 
-    private <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
+    private static <T> T resolveAsEnum(final Class<T> classDescriptor, final String value) {
         final T[] enumConstants = classDescriptor.getEnumConstants();
         if (enumConstants != null) {
             for (final T enm : classDescriptor.getEnumConstants()) {
@@ -1031,7 +1046,7 @@ public class RestconfImpl implements RestconfService {
         return null;
     }
 
-    private Map<String, String> resolveValuesFromUri(final String uri) {
+    private static Map<String, String> resolveValuesFromUri(final String uri) {
         final Map<String, String> result = new HashMap<>();
         final String[] tokens = uri.split("/");
         for (int i = 1; i < tokens.length; i++) {