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=f5219d9dda7e54d19632aaca198c455ccc9e7d90;hb=e9a4159827cbb646529221184a38c41d81ced4b7;hp=0e8971ff4cac835b7619d96b3c104b87a59b0197;hpb=6b8e4f7ee09ed344969ddad8017a7f41ad89f2d0;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 0e8971ff4c..f5219d9dda 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 @@ -7,74 +7,122 @@ */ package org.opendaylight.restconf.nb.rfc8040.rests.services.impl; -import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM; +import static java.util.Objects.requireNonNull; +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.STREAMS_PATH; 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; +import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_PATH_PART; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import java.net.URI; import java.time.Clock; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map.Entry; -import javax.annotation.Nonnull; +import java.util.Optional; +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.UriInfo; -import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; -import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint; -import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMMountPoint; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; 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.RestConnectorProvider; +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.references.SchemaContextRef; 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.TransactionVarsWrapper; +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.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.utils.RestconfConstants; +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.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.EffectiveModelContext; +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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of {@link RestconfDataService}. */ +@Path("/") public class RestconfDataServiceImpl implements RestconfDataService { + // FIXME: we should be able to interpret 'point' and refactor this class into a behavior + private static final class QueryParams implements Immutable { + final @Nullable String point; + final @Nullable Insert insert; + + QueryParams(final @Nullable Insert 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; + 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 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 = schemaContextHandler; - this.transactionChainHandler = transactionChainHandler; - this.mountPointServiceHandler = mountPointServiceHandler; - this.delegRestconfSubscrService = delegRestconfSubscrService; + final RestconfStreamsSubscriptionService delegRestconfSubscrService, + final ActionServiceHandler actionServiceHandler, + final Configuration configuration) { + this.actionServiceHandler = requireNonNull(actionServiceHandler); + this.schemaContextHandler = requireNonNull(schemaContextHandler); + this.transactionChainHandler = requireNonNull(transactionChainHandler); + this.mountPointServiceHandler = requireNonNull(mountPointServiceHandler); + this.delegRestconfSubscrService = requireNonNull(delegRestconfSubscrService); + streamUtils = configuration.isUseSSE() ? SubscribeToStreamUtil.serverSentEvents() + : SubscribeToStreamUtil.webSockets(); } @Override @@ -82,6 +130,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) { @@ -97,61 +147,20 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public Response readData(final String identifier, final UriInfo uriInfo) { - final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get()); + final EffectiveModelContext 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); + identifier, schemaContextRef, Optional.of(this.mountPointServiceHandler.get())); + final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(instanceIdentifier, uriInfo); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); - final DOMTransactionChain transactionChain; - if (mountPoint == null) { - transactionChain = this.transactionChainHandler.get(); - } else { - transactionChain = transactionChainOfMountPoint(mountPoint); - } - - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - instanceIdentifier, mountPoint, transactionChain); - final NormalizedNode node = - ReadDataTransactionUtil.readData(identifier, parameters.getContent(), transactionNode, withDefa, - schemaContextRef, uriInfo); - if (identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART) + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); + final NormalizedNode node = readData(identifier, parameters.getContent(), + instanceIdentifier.getInstanceIdentifier(), strategy, 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.toString() + RestconfConstants.SLASH), - value.length()); + final String streamName = value.substring(value.indexOf(NOTIFICATION_STREAM + '/')); this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo); } if (node == null) { @@ -161,12 +170,13 @@ public class RestconfDataServiceImpl implements RestconfDataService { RestconfError.ErrorTag.DATA_MISSING); } - if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL)) + if (parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL) || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) { + final QName type = node.getNodeType(); return Response.status(200) .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)) - .header("ETag", '"' + node.getNodeType().getModule().getFormattedRevision() - + node.getNodeType().getLocalName() + '"') + .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null) + + "-" + type.getLocalName() + '"') .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC()))) .build(); } @@ -174,70 +184,156 @@ public class RestconfDataServiceImpl implements RestconfDataService { return Response.status(200).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) { + strategy.prepareReadWriteExecution(); + final boolean exist = checkExist(schemaContext, strategy); + + for (final NotificationDefinition notificationDefinition : schemaContext.getNotifications()) { + final NotificationListenerAdapter notifiStreamXML = + CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, + NotificationOutputType.XML); + final NotificationListenerAdapter notifiStreamJSON = + CreateStreamUtil.createYangNotifiStream(notificationDefinition, schemaContext, + NotificationOutputType.JSON); + writeNotificationStreamToDatastore(schemaContext, uriInfo, strategy, exist, notifiStreamXML); + writeNotificationStreamToDatastore(schemaContext, uriInfo, strategy, exist, notifiStreamJSON); + } + try { + strategy.commit().get(); + } catch (final InterruptedException | ExecutionException e) { + throw new RestconfDocumentedException("Problem while putting data to DS.", e); + } + } + + private void writeNotificationStreamToDatastore(final EffectiveModelContext schemaContext, + final UriInfo uriInfo, final RestconfStrategy strategy, final boolean exist, + final NotificationListenerAdapter listener) { + final URI uri = streamUtils.prepareUriByStreamName(uriInfo, listener.getStreamName()); + final NormalizedNode mapToStreams = + RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring( + listener.getSchemaPath().lastNodeIdentifier(), schemaContext.getNotifications(), null, + listener.getOutputType(), uri, SubscribeToStreamUtil.getMonitoringModule(schemaContext), exist); + writeDataToDS(schemaContext, + listener.getSchemaPath().lastNodeIdentifier().getLocalName(), strategy, exist, mapToStreams); + } + + 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); + } + } + + private static void writeDataToDS(final EffectiveModelContext schemaContext, final String name, + final RestconfStrategy strategy, final boolean exist, + final NormalizedNode mapToStreams) { + final String pathId; + if (exist) { + pathId = Rfc8040.MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + name; + } else { + pathId = Rfc8040.MonitoringModule.PATH_TO_STREAMS; + } + strategy.merge(LogicalDatastoreType.OPERATIONAL, IdentifierCodec.deserialize(pathId, schemaContext), + mapToStreams); + } + @Override public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) { - Preconditions.checkNotNull(payload); + requireNonNull(payload); + + final QueryParams checkedParms = checkQueryParameters(uriInfo); + + 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 EffectiveModelContext ref = mountPoint == null + ? this.schemaContextHandler.get() : modelContext(mountPoint); + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); + return PutDataTransactionUtil.putData(payload, ref, strategy, checkedParms.insert, checkedParms.point); + } + + private static QueryParams checkQueryParameters(final UriInfo uriInfo) { boolean insertUsed = false; boolean pointUsed = false; - String insert = null; + Insert 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."); + case INSERT: + if (insertUsed) { + throw new RestconfDocumentedException("Insert parameter can be used only once.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); + } + + insertUsed = true; + final String str = entry.getValue().get(0); + insert = Insert.forValue(str); + if (insert == null) { + throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); } break; - case "point": - if (!pointUsed) { - pointUsed = true; - point = entry.getValue().iterator().next(); - } else { - throw new RestconfDocumentedException("Point parameter can be used only once."); + case POINT: + if (pointUsed) { + throw new RestconfDocumentedException("Point parameter can be used only once.", + RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT); } + + pointUsed = true; + point = entry.getValue().get(0); 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); - - 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 DOMTransactionChain transactionChain; - final SchemaContextRef ref; - if (mountPoint == null) { - transactionChain = this.transactionChainHandler.get(); - ref = new SchemaContextRef(this.schemaContextHandler.get()); - } else { - transactionChain = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); - } - - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, transactionChain); - return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point); + return new QueryParams(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."); + private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final Insert insert) { + if (pointUsed) { + if (!insertUsed) { + throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.", + RestconfError.ErrorType.PROTOCOL, RestconfError.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); + } } } @@ -248,70 +344,26 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) { - Preconditions.checkNotNull(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()); - } + requireNonNull(payload); + if (payload.getInstanceIdentifierContext().getSchemaNode() instanceof ActionDefinition) { + return invokeAction(payload); } - checkQueryParams(insertUsed, pointUsed, insert); - + final QueryParams checkedParms = checkQueryParameters(uriInfo); final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint(); - final DOMTransactionChain transactionChain; - final SchemaContextRef ref; - if (mountPoint == null) { - transactionChain = this.transactionChainHandler.get(); - ref = new SchemaContextRef(this.schemaContextHandler.get()); - } else { - transactionChain = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); - } - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - payload.getInstanceIdentifierContext(), mountPoint, transactionChain); - return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref, insert, point); + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); + return PostDataTransactionUtil.postData(uriInfo, payload, strategy, + getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point); } @Override public Response deleteData(final String identifier) { - final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier( - identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get())); + identifier, this.schemaContextHandler.get(), Optional.of(this.mountPointServiceHandler.get())); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); - final DOMTransactionChain transactionChain; - if (mountPoint == null) { - transactionChain = this.transactionChainHandler.get(); - } else { - transactionChain = transactionChainOfMountPoint(mountPoint); - } - - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint, - transactionChain); - return DeleteDataTransactionUtil.deleteData(transactionNode); + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); + return DeleteDataTransactionUtil.deleteData(strategy, instanceIdentifier.getInstanceIdentifier()); } @Override @@ -321,39 +373,100 @@ public class RestconfDataServiceImpl implements RestconfDataService { @Override public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) { - Preconditions.checkNotNull(context); - final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint(); + final DOMMountPoint mountPoint = requireNonNull(context).getInstanceIdentifierContext().getMountPoint(); + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); + return PatchDataTransactionUtil.patchData(context, strategy, 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 EffectiveModelContext ref = mountPoint == null + ? this.schemaContextHandler.get() : modelContext(mountPoint); + final RestconfStrategy strategy = getRestconfStrategy(mountPoint); - final DOMTransactionChain transactionChain; - final SchemaContextRef ref; + return PlainPatchDataTransactionUtil.patchData(payload, strategy, ref); + } + + private EffectiveModelContext getSchemaContext(final DOMMountPoint mountPoint) { + return mountPoint == null ? schemaContextHandler.get() : modelContext(mountPoint); + } + + // FIXME: why is this synchronized? + public synchronized RestconfStrategy getRestconfStrategy(final DOMMountPoint mountPoint) { if (mountPoint == null) { - transactionChain = this.transactionChainHandler.get(); - ref = new SchemaContextRef(this.schemaContextHandler.get()); - } else { - transactionChain = transactionChainOfMountPoint(mountPoint); - ref = new SchemaContextRef(mountPoint.getSchemaContext()); + return new MdsalRestconfStrategy(transactionChainHandler); } - final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( - context.getInstanceIdentifierContext(), mountPoint, transactionChain); - - return PatchDataTransactionUtil.patchData(context, transactionNode, ref); + return RestconfStrategy.forMountPoint(mountPoint).orElseThrow(() -> { + LOG.warn("Mount point {} does not expose a suitable access interface", mountPoint.getIdentifier()); + return new RestconfDocumentedException("Could not find a supported access interface in mount point " + + mountPoint.getIdentifier()); + }); } /** - * Prepare transaction chain to access data of mount point. - * @param mountPoint - * mount point reference - * @return {@link DOMTransactionChain} + * Invoke Action operation. + * + * @param payload {@link NormalizedNodeContext} - the body of the operation + * @return {@link NormalizedNodeContext} wrapped in {@link Response} */ - private static DOMTransactionChain transactionChainOfMountPoint(@Nonnull final DOMMountPoint mountPoint) { - final Optional domDataBrokerService = mountPoint.getService(DOMDataBroker.class); - if (domDataBrokerService.isPresent()) { - return domDataBrokerService.get().createTransactionChain(RestConnectorProvider.TRANSACTION_CHAIN_LISTENER); + public Response invokeAction(final NormalizedNodeContext payload) { + final InstanceIdentifierContext context = payload.getInstanceIdentifierContext(); + final DOMMountPoint mountPoint = context.getMountPoint(); + final Absolute schemaPath = Absolute.of(ImmutableList.copyOf(context.getSchemaNode().getPath() + .getPathFromRoot())); + 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 String errMsg = "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier(); - LOG.warn(errMsg); - throw new RestconfDocumentedException(errMsg); + final DOMActionResult response; + final EffectiveModelContext schemaContextRef; + if (mountPoint != null) { + response = RestconfInvokeOperationsUtil.invokeActionViaMountPoint(mountPoint, (ContainerNode) data, + schemaPath, yangIIdContext); + schemaContextRef = modelContext(mountPoint); + } else { + response = RestconfInvokeOperationsUtil.invokeAction((ContainerNode) data, schemaPath, + this.actionServiceHandler, yangIIdContext); + 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), resultData)).build(); + } + + private static EffectiveModelContext modelContext(final DOMMountPoint mountPoint) { + return mountPoint.getService(DOMSchemaService.class) + .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext())) + .orElse(null); } }