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 f0c972cdb0162c7c41f1711e64e07f989ee6fa24..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;
@@ -92,12 +94,11 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.util.EmptyType;
-import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 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;
 
@@ -109,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";
@@ -372,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());
@@ -392,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) {
@@ -435,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.");
@@ -465,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;
         }
@@ -491,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 {
@@ -503,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);
@@ -570,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);
         }
 
@@ -601,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);
         }
@@ -630,20 +628,11 @@ public class RestconfImpl implements RestconfService {
 
         final DOMRpcResult result = checkRpcResponse(response);
 
-        DataSchemaNode resultNodeSchema = null;
-        NormalizedNode<?, ?> resultData = null;
-        if (result != null && result.getResult() != null) {
-            resultData = result.getResult();
-            final ContainerSchemaNode rpcDataSchemaNode =
-                    SchemaContextUtil.getRpcDataSchema(schemaContext, rpc.getOutput().getPath());
-            resultNodeSchema = rpcDataSchemaNode.getDataChildByName(result.getResult().getNodeType());
-        }
-
-        return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, resultNodeSchema, mountPoint,
-                schemaContext), resultData, QueryParametersParser.parseWriterParameters(uriInfo));
+        return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, rpc, mountPoint, schemaContext),
+                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,13 +736,17 @@ public class RestconfImpl implements RestconfService {
                     LOG.debug("Update ConfigDataStore fail " + identifier, e);
                     throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
                 }
+            } catch (Exception e) {
+                final String errMsg = "Error updating data ";
+                LOG.debug(errMsg + identifier, e);
+                throw new RestconfDocumentedException(errMsg, e);
             }
         }
 
         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();
@@ -824,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);
@@ -881,7 +874,7 @@ public class RestconfImpl implements RestconfService {
             throw e;
         } catch (final Exception e) {
             final String errMsg = "Error creating data ";
-            LOG.info(errMsg + uriInfo.getPath(), e);
+            LOG.info(errMsg + (uriInfo != null ? uriInfo.getPath() : ""), e);
             throw new RestconfDocumentedException(errMsg, e);
         }
 
@@ -895,6 +888,11 @@ public class RestconfImpl implements RestconfService {
     }
 
     private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint, final YangInstanceIdentifier normalizedII) {
+        if(uriInfo == null) {
+            // This is null if invoked internally
+            return null;
+        }
+
         final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
         uriBuilder.path("config");
         try {
@@ -936,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
@@ -981,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
      *
@@ -988,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)) {
@@ -1013,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()) {
@@ -1032,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++) {