X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Frestconf-nb-rfc8040%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Frestconf%2Fnb%2Frfc8040%2Frests%2Fservices%2Fimpl%2FRestconfDataServiceImpl.java;h=a2454ceb3ee66854d8bad96c68e2fcbecb4f8aa6;hb=f15a2465d554d7de583eebd3d07027d4bf8cf31f;hp=7c4520f94970310b03a710257e8df73f7d80040e;hpb=c36a873a080a0754acf8ddda09b7532976968f86;p=netconf.git diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java index 7c4520f949..a2454ceb3e 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java @@ -17,43 +17,50 @@ import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsCo import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH; import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH_PART; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; import java.net.URI; import java.time.Clock; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.ws.rs.Path; -import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMActionException; import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMActionService; +import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.context.WriterParameters; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; -import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; -import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; import org.opendaylight.restconf.common.patch.PatchContext; import org.opendaylight.restconf.common.patch.PatchStatusContext; import org.opendaylight.restconf.nb.rfc8040.Rfc8040; -import org.opendaylight.restconf.nb.rfc8040.handlers.ActionServiceHandler; -import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfDataService; import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService; import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy; import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy; -import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfTransaction; import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.PlainPatchDataTransactionUtil; @@ -62,22 +69,27 @@ import org.opendaylight.restconf.nb.rfc8040.rests.utils.PutDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil; import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant; import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.Insert; -import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfInvokeOperationsUtil; import org.opendaylight.restconf.nb.rfc8040.streams.Configuration; import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter; import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil; -import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType; import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.ActionDefinition; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; @@ -104,44 +116,27 @@ public class RestconfDataServiceImpl implements RestconfDataService { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss"); private final RestconfStreamsSubscriptionService delegRestconfSubscrService; + private final SchemaContextHandler schemaContextHandler; + private final MdsalRestconfStrategy restconfStrategy; + private final DOMMountPointService mountPointService; private final SubscribeToStreamUtil streamUtils; - - // FIXME: evaluate thread-safety of updates (synchronized) vs. access (mostly unsynchronized) here - private SchemaContextHandler schemaContextHandler; - private TransactionChainHandler transactionChainHandler; - private DOMMountPointServiceHandler mountPointServiceHandler; - private volatile ActionServiceHandler actionServiceHandler; + private final DOMActionService actionService; + private final DOMDataBroker dataBroker; public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler, - final TransactionChainHandler transactionChainHandler, - final DOMMountPointServiceHandler mountPointServiceHandler, + final DOMDataBroker dataBroker, final DOMMountPointService mountPointService, final RestconfStreamsSubscriptionService delegRestconfSubscrService, - final ActionServiceHandler actionServiceHandler, - final Configuration configuration) { - this.actionServiceHandler = requireNonNull(actionServiceHandler); + final DOMActionService actionService, final Configuration configuration) { this.schemaContextHandler = requireNonNull(schemaContextHandler); - this.transactionChainHandler = requireNonNull(transactionChainHandler); - this.mountPointServiceHandler = requireNonNull(mountPointServiceHandler); + this.dataBroker = requireNonNull(dataBroker); + this.restconfStrategy = new MdsalRestconfStrategy(dataBroker); + this.mountPointService = requireNonNull(mountPointService); this.delegRestconfSubscrService = requireNonNull(delegRestconfSubscrService); + this.actionService = requireNonNull(actionService); streamUtils = configuration.isUseSSE() ? SubscribeToStreamUtil.serverSentEvents() : SubscribeToStreamUtil.webSockets(); } - @Override - public synchronized void updateHandlers(final Object... handlers) { - for (final Object object : handlers) { - if (object instanceof SchemaContextHandler) { - schemaContextHandler = (SchemaContextHandler) object; - } else if (object instanceof ActionServiceHandler) { - actionServiceHandler = (ActionServiceHandler) object; - } else if (object instanceof DOMMountPointServiceHandler) { - mountPointServiceHandler = (DOMMountPointServiceHandler) object; - } else if (object instanceof TransactionChainHandler) { - transactionChainHandler = (TransactionChainHandler) object; - } - } - } - @Override public Response readData(final UriInfo uriInfo) { return readData(null, uriInfo); @@ -151,36 +146,44 @@ public class RestconfDataServiceImpl implements RestconfDataService { public Response readData(final String identifier, final UriInfo uriInfo) { final EffectiveModelContext schemaContextRef = this.schemaContextHandler.get(); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier( - identifier, schemaContextRef, Optional.of(this.mountPointServiceHandler.get())); + identifier, schemaContextRef, Optional.of(mountPointService)); final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); + + // FIXME: this looks quite crazy, why do we even have it? + if (mountPoint == null && identifier != null && identifier.contains(STREAMS_PATH) + && !identifier.contains(STREAM_PATH_PART)) { + createAllYangNotificationStreams(schemaContextRef, uriInfo); + } + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); - final NormalizedNode node; + final NormalizedNode node; if (parameters.getFieldPaths() != null && !parameters.getFieldPaths().isEmpty()) { node = ReadDataTransactionUtil.readData(parameters.getContent(), instanceIdentifier.getInstanceIdentifier(), strategy, parameters.getWithDefault(), schemaContextRef, parameters.getFieldPaths()); } else { - node = readData(identifier, parameters.getContent(), instanceIdentifier.getInstanceIdentifier(), strategy, - parameters.getWithDefault(), schemaContextRef, uriInfo); + node = ReadDataTransactionUtil.readData(parameters.getContent(), instanceIdentifier.getInstanceIdentifier(), + strategy, parameters.getWithDefault(), schemaContextRef); } + + // FIXME: this is utter craziness, refactor it properly! if (identifier != null && identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART) && identifier.contains(STREAM_LOCATION_PATH_PART)) { - final String value = (String) node.getValue(); + final String value = (String) node.body(); final String streamName = value.substring(value.indexOf(NOTIFICATION_STREAM + '/')); this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo); } if (node == null) { throw new RestconfDocumentedException( "Request could not be completed because the relevant data model content does not exist", - RestconfError.ErrorType.PROTOCOL, - RestconfError.ErrorTag.DATA_MISSING); + ErrorType.PROTOCOL, ErrorTag.DATA_MISSING); } if (parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL) || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) { - final QName type = node.getNodeType(); - return Response.status(200) + final QName type = node.getIdentifier().getNodeType(); + return Response.status(Status.OK) .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)) .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null) + "-" + type.getLocalName() + '"') @@ -188,44 +191,20 @@ public class RestconfDataServiceImpl implements RestconfDataService { .build(); } - return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build(); + return Response.status(Status.OK) + .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)) + .build(); } - /** - * Read specific type of data from data store via transaction and if identifier read data from - * streams then put streams from actual schema context to datastore. - * - * @param identifier identifier of data to read - * @param content type of data to read (config, state, all) - * @param strategy {@link RestconfStrategy} - object that perform the actual DS operations - * @param withDefa value of with-defaults parameter - * @param schemaContext schema context - * @param uriInfo uri info - * @return {@link NormalizedNode} - */ - private NormalizedNode readData(final String identifier, final String content, - final YangInstanceIdentifier path, final RestconfStrategy strategy, final String withDefa, - final EffectiveModelContext schemaContext, final UriInfo uriInfo) { - if (identifier != null && identifier.contains(STREAMS_PATH) && !identifier.contains(STREAM_PATH_PART)) { - createAllYangNotificationStreams(strategy, schemaContext, uriInfo); - } - return ReadDataTransactionUtil.readData(content, path, strategy, withDefa, schemaContext); - } - - private void createAllYangNotificationStreams(final RestconfStrategy strategy, - final EffectiveModelContext schemaContext, final UriInfo uriInfo) { - final RestconfTransaction transaction = strategy.prepareWriteExecution(); - final boolean exist = checkExist(schemaContext, strategy); - + private void createAllYangNotificationStreams(final EffectiveModelContext schemaContext, final UriInfo uriInfo) { + final DOMDataTreeWriteTransaction transaction = dataBroker.newWriteOnlyTransaction(); for (final NotificationDefinition notificationDefinition : schemaContext.getNotifications()) { - final NotificationListenerAdapter notifiStreamXML = + writeNotificationStreamToDatastore(schemaContext, uriInfo, transaction, CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, - NotificationOutputType.XML); - final NotificationListenerAdapter notifiStreamJSON = + NotificationOutputType.XML)); + writeNotificationStreamToDatastore(schemaContext, uriInfo, transaction, CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, - NotificationOutputType.JSON); - writeNotificationStreamToDatastore(schemaContext, uriInfo, transaction, exist, notifiStreamXML); - writeNotificationStreamToDatastore(schemaContext, uriInfo, transaction, exist, notifiStreamJSON); + NotificationOutputType.JSON)); } try { transaction.commit().get(); @@ -235,31 +214,14 @@ public class RestconfDataServiceImpl implements RestconfDataService { } private void writeNotificationStreamToDatastore(final EffectiveModelContext schemaContext, - final UriInfo uriInfo, final RestconfTransaction transaction, final boolean exist, - final NotificationListenerAdapter listener) { + final UriInfo uriInfo, final DOMDataTreeWriteOperations tx, final NotificationListenerAdapter listener) { final URI uri = streamUtils.prepareUriByStreamName(uriInfo, listener.getStreamName()); final MapEntryNode mapToStreams = RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring( listener.getSchemaPath().lastNodeIdentifier(), schemaContext.getNotifications(), null, - listener.getOutputType(), uri, SubscribeToStreamUtil.getMonitoringModule(schemaContext)); - - final String name = listener.getSchemaPath().lastNodeIdentifier().getLocalName(); - final String pathId; - if (exist) { - pathId = Rfc8040.MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + name; - } else { - pathId = Rfc8040.MonitoringModule.PATH_TO_STREAMS; - } - transaction.merge(LogicalDatastoreType.OPERATIONAL, IdentifierCodec.deserialize(pathId, schemaContext), - mapToStreams); - } + listener.getOutputType(), uri); - private static boolean checkExist(final EffectiveModelContext schemaContext, final RestconfStrategy strategy) { - try { - return strategy.exists(LogicalDatastoreType.OPERATIONAL, - IdentifierCodec.deserialize(Rfc8040.MonitoringModule.PATH_TO_STREAMS, schemaContext)).get(); - } catch (final InterruptedException | ExecutionException exception) { - throw new RestconfDocumentedException("Problem while checking data if exists", exception); - } + tx.merge(LogicalDatastoreType.OPERATIONAL, + Rfc8040.restconfStateStreamPath(mapToStreams.getIdentifier()), mapToStreams); } @Override @@ -270,9 +232,9 @@ public class RestconfDataServiceImpl implements RestconfDataService { final InstanceIdentifierContext iid = payload.getInstanceIdentifierContext(); - PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload); - PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload); - PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload); + validInputData(iid.getSchemaNode(), payload); + validTopLevelNodeName(iid.getInstanceIdentifier(), payload); + validateListKeysEqualityInPayloadAndUri(payload); final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); final EffectiveModelContext ref = mountPoint == null @@ -293,7 +255,7 @@ public class RestconfDataServiceImpl implements RestconfDataService { case INSERT: if (insertUsed) { throw new RestconfDocumentedException("Insert parameter can be used only once.", - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } insertUsed = true; @@ -301,13 +263,13 @@ public class RestconfDataServiceImpl implements RestconfDataService { insert = Insert.forValue(str); if (insert == null) { throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'", - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } break; case POINT: if (pointUsed) { throw new RestconfDocumentedException("Point parameter can be used only once.", - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } pointUsed = true; @@ -315,7 +277,7 @@ public class RestconfDataServiceImpl implements RestconfDataService { break; default: throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey(), - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } } @@ -327,13 +289,13 @@ public class RestconfDataServiceImpl implements RestconfDataService { if (pointUsed) { if (!insertUsed) { throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.", - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } if (insert != Insert.BEFORE && insert != Insert.AFTER) { throw new RestconfDocumentedException( "Point parameter can be used only with 'after' or 'before' values of Insert parameter.", - RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT); } } } @@ -360,7 +322,7 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public Response deleteData(final String identifier) { final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier( - identifier, this.schemaContextHandler.get(), Optional.of(this.mountPointServiceHandler.get())); + identifier, this.schemaContextHandler.get(), Optional.of(mountPointService)); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); final RestconfStrategy strategy = getRestconfStrategy(mountPoint); @@ -374,7 +336,9 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) { - final DOMMountPoint mountPoint = requireNonNull(context).getInstanceIdentifierContext().getMountPoint(); + final DOMMountPoint mountPoint = RestconfDocumentedException.throwIfNull(context, + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "No patch documented provided") + .getInstanceIdentifierContext().getMountPoint(); final RestconfStrategy strategy = getRestconfStrategy(mountPoint); return PatchDataTransactionUtil.patchData(context, strategy, getSchemaContext(mountPoint)); } @@ -383,12 +347,10 @@ public class RestconfDataServiceImpl implements RestconfDataService { public Response patchData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) { requireNonNull(payload); - final InstanceIdentifierContext iid = payload - .getInstanceIdentifierContext(); - - PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload); - PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload); - PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload); + final InstanceIdentifierContext iid = payload.getInstanceIdentifierContext(); + validInputData(iid.getSchemaNode(), payload); + validTopLevelNodeName(iid.getInstanceIdentifier(), payload); + validateListKeysEqualityInPayloadAndUri(payload); final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); final EffectiveModelContext ref = mountPoint == null @@ -405,7 +367,7 @@ public class RestconfDataServiceImpl implements RestconfDataService { // FIXME: why is this synchronized? public synchronized RestconfStrategy getRestconfStrategy(final DOMMountPoint mountPoint) { if (mountPoint == null) { - return new MdsalRestconfStrategy(transactionChainHandler); + return restconfStrategy; } return RestconfStrategy.forMountPoint(mountPoint).orElseThrow(() -> { @@ -427,9 +389,10 @@ public class RestconfDataServiceImpl implements RestconfDataService { final Absolute schemaPath = Absolute.of(ImmutableList.copyOf(context.getSchemaNode().getPath() .getPathFromRoot())); final YangInstanceIdentifier yangIIdContext = context.getInstanceIdentifier(); - final NormalizedNode data = payload.getData(); + final NormalizedNode data = payload.getData(); - if (yangIIdContext.isEmpty() && !RestconfDataServiceConstant.NETCONF_BASE_QNAME.equals(data.getNodeType())) { + if (yangIIdContext.isEmpty() + && !RestconfDataServiceConstant.NETCONF_BASE_QNAME.equals(data.getIdentifier().getNodeType())) { throw new RestconfDocumentedException("Instance identifier need to contain at least one path argument", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } @@ -437,15 +400,13 @@ public class RestconfDataServiceImpl implements RestconfDataService { final DOMActionResult response; final EffectiveModelContext schemaContextRef; if (mountPoint != null) { - response = RestconfInvokeOperationsUtil.invokeActionViaMountPoint(mountPoint, (ContainerNode) data, - schemaPath, yangIIdContext); + response = invokeAction((ContainerNode) data, schemaPath, yangIIdContext, mountPoint); schemaContextRef = modelContext(mountPoint); } else { - response = RestconfInvokeOperationsUtil.invokeAction((ContainerNode) data, schemaPath, - this.actionServiceHandler, yangIIdContext); - schemaContextRef = this.schemaContextHandler.get(); + response = invokeAction((ContainerNode) data, schemaPath, yangIIdContext, actionService); + schemaContextRef = schemaContextHandler.get(); } - final DOMActionResult result = RestconfInvokeOperationsUtil.checkActionResponse(response); + final DOMActionResult result = checkActionResponse(response); ActionDefinition resultNodeSchema = null; ContainerNode resultData = null; @@ -457,12 +418,156 @@ public class RestconfDataServiceImpl implements RestconfDataService { } } - if (resultData != null && resultData.getValue().isEmpty()) { - throw new WebApplicationException(Response.Status.NO_CONTENT); + if (resultData != null && resultData.isEmpty()) { + return Response.status(Status.NO_CONTENT).build(); } - return Response.status(200).entity(new NormalizedNodeContext(new InstanceIdentifierContext<>(yangIIdContext, - resultNodeSchema, mountPoint, schemaContextRef), resultData)).build(); + return Response.status(Status.OK) + .entity(new NormalizedNodeContext( + new InstanceIdentifierContext<>(yangIIdContext, resultNodeSchema, mountPoint, schemaContextRef), + resultData)) + .build(); + } + + + /** + * Invoking Action via mount point. + * + * @param mountPoint mount point + * @param data input data + * @param schemaPath schema path of data + * @return {@link DOMActionResult} + */ + private static DOMActionResult invokeAction(final ContainerNode data, + final Absolute schemaPath, final YangInstanceIdentifier yangIId, final DOMMountPoint mountPoint) { + return invokeAction(data, schemaPath, yangIId, mountPoint.getService(DOMActionService.class) + .orElseThrow(() -> new RestconfDocumentedException("DomAction service is missing."))); + } + + /** + * Invoke Action via ActionServiceHandler. + * + * @param data input data + * @param yangIId invocation context + * @param schemaPath schema path of data + * @param actionService action service to invoke action + * @return {@link DOMActionResult} + */ + // FIXME: NETCONF-718: we should be returning a future here + private static DOMActionResult invokeAction(final ContainerNode data, final Absolute schemaPath, + final YangInstanceIdentifier yangIId, final DOMActionService actionService) { + return RestconfInvokeOperationsServiceImpl.checkedGet(Futures.catching(actionService.invokeAction( + schemaPath, new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangIId.getParent()), data), + DOMActionException.class, + cause -> new SimpleDOMActionResult(ImmutableList.of(RpcResultBuilder.newError( + RpcError.ErrorType.RPC, "operation-failed", cause.getMessage()))), + MoreExecutors.directExecutor())); + } + + /** + * Check the validity of the result. + * + * @param response response of Action + * @return {@link DOMActionResult} result + */ + private static DOMActionResult checkActionResponse(final DOMActionResult response) { + if (response == null) { + return null; + } + + try { + if (response.getErrors().isEmpty()) { + return response; + } + LOG.debug("InvokeAction Error Message {}", response.getErrors()); + throw new RestconfDocumentedException("InvokeAction Error Message ", null, response.getErrors()); + } catch (final CancellationException e) { + final String errMsg = "The Action Operation was cancelled while executing."; + LOG.debug("Cancel Execution: {}", errMsg, e); + throw new RestconfDocumentedException(errMsg, ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, e); + } + } + + /** + * Valid input data with {@link SchemaNode}. + * + * @param schemaNode {@link SchemaNode} + * @param payload input data + */ + @VisibleForTesting + public static void validInputData(final SchemaNode schemaNode, final NormalizedNodeContext payload) { + if (schemaNode != null && payload.getData() == null) { + throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } else if (schemaNode == null && payload.getData() != null) { + throw new RestconfDocumentedException("No input expected.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + } + + /** + * Valid top level node name. + * + * @param path path of node + * @param payload data + */ + @VisibleForTesting + public static void validTopLevelNodeName(final YangInstanceIdentifier path, final NormalizedNodeContext payload) { + final String payloadName = payload.getData().getIdentifier().getNodeType().getLocalName(); + + if (path.isEmpty()) { + if (!payload.getData().getIdentifier().getNodeType().equals( + RestconfDataServiceConstant.NETCONF_BASE_QNAME)) { + throw new RestconfDocumentedException("Instance identifier has to contain at least one path argument", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + } else { + final String identifierName = path.getLastPathArgument().getNodeType().getLocalName(); + if (!payloadName.equals(identifierName)) { + throw new RestconfDocumentedException( + "Payload name (" + payloadName + ") is different from identifier name (" + identifierName + ")", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + } + } + + + /** + * Validates whether keys in {@code payload} are equal to values of keys in + * {@code iiWithData} for list schema node. + * + * @throws RestconfDocumentedException if key values or key count in payload and URI isn't equal + */ + @VisibleForTesting + public static void validateListKeysEqualityInPayloadAndUri(final NormalizedNodeContext payload) { + final InstanceIdentifierContext iiWithData = payload.getInstanceIdentifierContext(); + final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument(); + final SchemaNode schemaNode = iiWithData.getSchemaNode(); + final NormalizedNode data = payload.getData(); + if (schemaNode instanceof ListSchemaNode) { + final List keyDefinitions = ((ListSchemaNode) schemaNode).getKeyDefinition(); + if (lastPathArgument instanceof NodeIdentifierWithPredicates && data instanceof MapEntryNode) { + final Map uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument).asMap(); + isEqualUriAndPayloadKeyValues(uriKeyValues, (MapEntryNode) data, keyDefinitions); + } + } + } + + private static void isEqualUriAndPayloadKeyValues(final Map uriKeyValues, final MapEntryNode payload, + final List keyDefinitions) { + final Map mutableCopyUriKeyValues = new HashMap<>(uriKeyValues); + for (final QName keyDefinition : keyDefinitions) { + final Object uriKeyValue = RestconfDocumentedException.throwIfNull( + mutableCopyUriKeyValues.remove(keyDefinition), ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, + "Missing key %s in URI.", keyDefinition); + + final Object dataKeyValue = payload.getIdentifier().getValue(keyDefinition); + + if (!uriKeyValue.equals(dataKeyValue)) { + final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName() + + "' specified in the URI doesn't match the value '" + dataKeyValue + + "' specified in the message body. "; + throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); + } + } } private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) {