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=389643b5e77f7c42d7d0c677abb88f3069e96d6f;hb=98389a986a692c7906f9ad25e66fc86962599d85;hp=55f3cd8ee1495a620974f53ac2aac6270c175574;hpb=17a709f581167067651f0b978d2f6feefa0302a9;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 55f3cd8ee1..389643b5e7 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 @@ -8,7 +8,9 @@ package org.opendaylight.restconf.nb.rfc8040.rests.services.impl; import static java.util.Objects.requireNonNull; -import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM; +import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.INSERT; +import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.POINT; +import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.NOTIFICATION_STREAM; import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_ACCESS_PATH_PART; import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_LOCATION_PATH_PART; import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH; @@ -18,12 +20,14 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map.Entry; -import java.util.Objects; import java.util.Optional; import javax.ws.rs.Path; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.dom.api.DOMActionResult; import org.opendaylight.mdsal.dom.api.DOMDataBroker; import org.opendaylight.mdsal.dom.api.DOMMountPoint; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; @@ -31,8 +35,11 @@ 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.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; @@ -42,16 +49,23 @@ import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSu import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; 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; import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil; 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.RestconfInvokeOperationsUtil; import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; +import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.ActionDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,24 +74,37 @@ import org.slf4j.LoggerFactory; */ @Path("/") public class RestconfDataServiceImpl implements RestconfDataService { + private static final class QueryParams implements Immutable { + final @Nullable String point; + final @Nullable String insert; + + QueryParams(final @Nullable String insert, final @Nullable String point) { + this.insert = insert; + this.point = point; + } + } private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss"); + private final RestconfStreamsSubscriptionService delegRestconfSubscrService; + + // FIXME: evaluate thread-safety of updates (synchronized) vs. access (mostly unsynchronized) here private SchemaContextHandler schemaContextHandler; private TransactionChainHandler transactionChainHandler; private DOMMountPointServiceHandler mountPointServiceHandler; - - private final RestconfStreamsSubscriptionService delegRestconfSubscrService; + private volatile ActionServiceHandler actionServiceHandler; public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler, - final TransactionChainHandler transactionChainHandler, + final TransactionChainHandler transactionChainHandler, final DOMMountPointServiceHandler mountPointServiceHandler, - final RestconfStreamsSubscriptionService delegRestconfSubscrService) { - this.schemaContextHandler = Objects.requireNonNull(schemaContextHandler); - this.transactionChainHandler = Objects.requireNonNull(transactionChainHandler); - this.mountPointServiceHandler = Objects.requireNonNull(mountPointServiceHandler); - this.delegRestconfSubscrService = Objects.requireNonNull(delegRestconfSubscrService); + final RestconfStreamsSubscriptionService delegRestconfSubscrService, + final ActionServiceHandler actionServiceHandler) { + this.actionServiceHandler = requireNonNull(actionServiceHandler); + this.schemaContextHandler = requireNonNull(schemaContextHandler); + this.transactionChainHandler = requireNonNull(transactionChainHandler); + this.mountPointServiceHandler = requireNonNull(mountPointServiceHandler); + this.delegRestconfSubscrService = requireNonNull(delegRestconfSubscrService); } @Override @@ -85,6 +112,8 @@ public class RestconfDataServiceImpl implements RestconfDataService { 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) { @@ -103,57 +132,18 @@ public class RestconfDataServiceImpl implements RestconfDataService { final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier( identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get())); - - boolean withDefaUsed = false; - String withDefa = null; - - for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { - switch (entry.getKey()) { - case "with-defaults": - if (!withDefaUsed) { - withDefaUsed = true; - withDefa = entry.getValue().iterator().next(); - } else { - throw new RestconfDocumentedException("With-defaults parameter can be used only once."); - } - break; - default: - LOG.info("Unknown key : {}.", entry.getKey()); - break; - } - } - boolean tagged = false; - if (withDefaUsed) { - if ("report-all-tagged".equals(withDefa)) { - tagged = true; - withDefa = null; - } - if ("report-all".equals(withDefa)) { - withDefa = null; - } - } - - final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters( - instanceIdentifier, uriInfo, tagged); + final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); - final TransactionChainHandler localTransactionChainHandler; - if (mountPoint == null) { - localTransactionChainHandler = this.transactionChainHandler; - } else { - localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - } - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - instanceIdentifier, mountPoint, localTransactionChainHandler); - final NormalizedNode node = - ReadDataTransactionUtil.readData(identifier, parameters.getContent(), transactionNode, withDefa, - schemaContextRef, uriInfo); + instanceIdentifier, mountPoint, getTransactionChainHandler(mountPoint)); + final NormalizedNode node = ReadDataTransactionUtil.readData(identifier, parameters.getContent(), + transactionNode, parameters.getWithDefault(), schemaContextRef, uriInfo); 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 streamName = value.substring( - value.indexOf(CREATE_NOTIFICATION_STREAM + RestconfConstants.SLASH)); + value.indexOf(NOTIFICATION_STREAM + RestconfConstants.SLASH)); this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo); } if (node == null) { @@ -181,35 +171,7 @@ public class RestconfDataServiceImpl implements RestconfDataService { public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) { requireNonNull(payload); - boolean insertUsed = false; - boolean pointUsed = false; - String insert = null; - String point = null; - - for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { - switch (entry.getKey()) { - case "insert": - if (!insertUsed) { - insertUsed = true; - insert = entry.getValue().iterator().next(); - } else { - throw new RestconfDocumentedException("Insert parameter can be used only once."); - } - break; - case "point": - if (!pointUsed) { - pointUsed = true; - point = entry.getValue().iterator().next(); - } else { - throw new RestconfDocumentedException("Point parameter can be used only once."); - } - break; - default: - throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey()); - } - } - - checkQueryParams(insertUsed, pointUsed, insert); + final QueryParams checkedParms = checkQueryParameters(uriInfo); final InstanceIdentifierContext iid = payload .getInstanceIdentifierContext(); @@ -226,33 +188,15 @@ public class RestconfDataServiceImpl implements RestconfDataService { ref = new SchemaContextRef(this.schemaContextHandler.get()); } else { localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); + ref = new SchemaContextRef(mountPoint.getEffectiveModelContext()); } final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); - return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point); - } - - private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) { - if (pointUsed && !insertUsed) { - throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter."); - } - if (pointUsed && (insert.equals("first") || insert.equals("last"))) { - throw new RestconfDocumentedException( - "Point parameter can be used only with 'after' or 'before' values of Insert parameter."); - } - } - - @Override - public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) { - return postData(payload, uriInfo); + return PutDataTransactionUtil.putData(payload, ref, transactionNode, checkedParms.insert, checkedParms.point); } - @Override - public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) { - requireNonNull(payload); - + private static QueryParams checkQueryParameters(final UriInfo uriInfo) { boolean insertUsed = false; boolean pointUsed = false; String insert = null; @@ -260,42 +204,65 @@ public class RestconfDataServiceImpl implements RestconfDataService { for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { switch (entry.getKey()) { - case "insert": + case INSERT: if (!insertUsed) { insertUsed = true; - insert = entry.getValue().iterator().next(); + insert = entry.getValue().get(0); } else { - throw new RestconfDocumentedException("Insert parameter can be used only once."); + throw new RestconfDocumentedException("Insert parameter can be used only once.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); } break; - case "point": + case POINT: if (!pointUsed) { pointUsed = true; - point = entry.getValue().iterator().next(); + point = entry.getValue().get(0); } else { - throw new RestconfDocumentedException("Point parameter can be used only once."); + throw new RestconfDocumentedException("Point parameter can be used only once.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); } break; default: - throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey()); + throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey(), + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); } } checkQueryParams(insertUsed, pointUsed, insert); + return new QueryParams(insert, point); + } - final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); - final TransactionChainHandler localTransactionChainHandler; - final SchemaContextRef ref; - if (mountPoint == null) { - localTransactionChainHandler = this.transactionChainHandler; - ref = new SchemaContextRef(this.schemaContextHandler.get()); - } else { - localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); + private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) { + if (pointUsed && !insertUsed) { + throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + } + if (pointUsed && (insert.equals("first") || insert.equals("last"))) { + throw new RestconfDocumentedException( + "Point parameter can be used only with 'after' or 'before' values of Insert parameter.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + } + } + + @Override + public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) { + return postData(payload, uriInfo); + } + + @Override + public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) { + requireNonNull(payload); + if (payload.getInstanceIdentifierContext().getSchemaNode() instanceof ActionDefinition) { + return invokeAction(payload, uriInfo); } + + final QueryParams checkedParms = checkQueryParameters(uriInfo); + + final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); - return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref, insert, point); + payload.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint)); + return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, + getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point); } @Override @@ -325,7 +292,23 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) { final DOMMountPoint mountPoint = requireNonNull(context).getInstanceIdentifierContext().getMountPoint(); + final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( + context.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint)); + return PatchDataTransactionUtil.patchData(context, transactionNode, getSchemaContext(mountPoint)); + } + + @Override + 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 DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); final TransactionChainHandler localTransactionChainHandler; final SchemaContextRef ref; if (mountPoint == null) { @@ -333,13 +316,22 @@ public class RestconfDataServiceImpl implements RestconfDataService { ref = new SchemaContextRef(this.schemaContextHandler.get()); } else { localTransactionChainHandler = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); + ref = new SchemaContextRef(mountPoint.getEffectiveModelContext()); } final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - context.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); + payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler); + + return PlainPatchDataTransactionUtil.patchData(payload, transactionNode, ref); + } + + private TransactionChainHandler getTransactionChainHandler(final DOMMountPoint mountPoint) { + return mountPoint == null ? transactionChainHandler : transactionChainOfMountPoint(mountPoint); + } - return PatchDataTransactionUtil.patchData(context, transactionNode, ref); + private SchemaContextRef getSchemaContext(final DOMMountPoint mountPoint) { + return mountPoint == null ? new SchemaContextRef(schemaContextHandler.get()) + : new SchemaContextRef(mountPoint.getEffectiveModelContext()); } /** @@ -358,4 +350,56 @@ public class RestconfDataServiceImpl implements RestconfDataService { LOG.warn(errMsg); throw new RestconfDocumentedException(errMsg); } + + /** + * Invoke Action operation. + * + * @param payload + * {@link NormalizedNodeContext} - the body of the operation + * @param uriInfo + * URI info + * @return {@link NormalizedNodeContext} wrapped in {@link Response} + */ + public Response invokeAction(final NormalizedNodeContext payload, final UriInfo uriInfo) { + final InstanceIdentifierContext context = payload.getInstanceIdentifierContext(); + final DOMMountPoint mountPoint = context.getMountPoint(); + final SchemaPath schemaPath = context.getSchemaNode().getPath(); + final YangInstanceIdentifier yangIIdContext = context.getInstanceIdentifier(); + final NormalizedNode data = payload.getData(); + + if (yangIIdContext.isEmpty() && !RestconfDataServiceConstant.NETCONF_BASE_QNAME.equals(data.getNodeType())) { + throw new RestconfDocumentedException("Instance identifier need to contain at least one path argument", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + + final DOMActionResult response; + final SchemaContextRef schemaContextRef; + if (mountPoint != null) { + response = RestconfInvokeOperationsUtil.invokeActionViaMountPoint(mountPoint, (ContainerNode) data, + schemaPath, yangIIdContext); + schemaContextRef = new SchemaContextRef(mountPoint.getEffectiveModelContext()); + } else { + response = RestconfInvokeOperationsUtil.invokeAction((ContainerNode) data, schemaPath, + this.actionServiceHandler, yangIIdContext); + schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get()); + } + final DOMActionResult result = RestconfInvokeOperationsUtil.checkActionResponse(response); + + ActionDefinition resultNodeSchema = null; + ContainerNode resultData = null; + if (result != null) { + final Optional optOutput = result.getOutput(); + if (optOutput.isPresent()) { + resultData = optOutput.get(); + resultNodeSchema = (ActionDefinition) context.getSchemaNode(); + } + } + + if (resultData != null && resultData.getValue().isEmpty()) { + throw new WebApplicationException(Response.Status.NO_CONTENT); + } + + return Response.status(200).entity(new NormalizedNodeContext(new InstanceIdentifierContext<>(yangIIdContext, + resultNodeSchema, mountPoint, schemaContextRef.get()), resultData)).build(); + } }