import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
+import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
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.data.impl.schema.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
this(databindProvider, dataBroker, rpcService, actionService, mountPointService, List.of(localRpcs));
}
+ @Override
+ public RestconfFuture<NormalizedNodePayload> dataGET(final ReadDataParams readParams) {
+ return readData(bindRequestRoot(), readParams);
+ }
+
+ @Override
+ public RestconfFuture<NormalizedNodePayload> dataGET(final String identifier, final ReadDataParams readParams) {
+ return readData(bindRequestPath(identifier), readParams);
+ }
+
+ private @NonNull RestconfFuture<NormalizedNodePayload> readData(final InstanceIdentifierContext reqPath,
+ final ReadDataParams readParams) {
+ final var queryParams = QueryParams.newQueryParameters(readParams, reqPath);
+ final var fieldPaths = queryParams.fieldPaths();
+ final var strategy = getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
+ final NormalizedNode node;
+ if (fieldPaths != null && !fieldPaths.isEmpty()) {
+ node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
+ readParams.withDefaults(), fieldPaths);
+ } else {
+ node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
+ readParams.withDefaults());
+ }
+ if (node == null) {
+ return RestconfFuture.failed(new RestconfDocumentedException(
+ "Request could not be completed because the relevant data model content does not exist",
+ ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
+ }
+
+ return RestconfFuture.of(new NormalizedNodePayload(reqPath.inference(), node, queryParams));
+ }
+
+ // FIXME: should follow the same pattern as operationsPOST() does
RestconfFuture<DOMActionResult> dataInvokePOST(final InstanceIdentifierContext reqPath,
final OperationInputBody body) {
final var yangIIdContext = reqPath.getInstanceIdentifier();
import org.opendaylight.mdsal.dom.api.DOMActionResult;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.yangtools.yang.common.Empty;
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.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
* Get target data resource from data root.
*
* @param uriInfo URI info
- * @return {@link NormalizedNodePayload}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@GET
@Path("/data")
MediaType.APPLICATION_XML,
MediaType.TEXT_XML
})
- public Response readData(@Context final UriInfo uriInfo) {
+ public void dataGET(@Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
final var readParams = QueryParams.newReadDataParams(uriInfo);
- return readData(server.bindRequestRoot(), readParams);
+ completeDataGET(server.dataGET(readParams), readParams, ar);
}
/**
*
* @param identifier path to target
* @param uriInfo URI info
- * @return {@link NormalizedNodePayload}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@GET
@Path("/data/{identifier:.+}")
MediaType.APPLICATION_XML,
MediaType.TEXT_XML
})
- public Response readData(@Encoded @PathParam("identifier") final String identifier,
- @Context final UriInfo uriInfo) {
+ public void dataGET(@Encoded @PathParam("identifier") final String identifier,
+ @Context final UriInfo uriInfo, @Suspended final AsyncResponse ar) {
final var readParams = QueryParams.newReadDataParams(uriInfo);
- return readData(server.bindRequestPath(identifier), readParams);
+ completeDataGET(server.dataGET(identifier, readParams), readParams, ar);
}
- private Response readData(final InstanceIdentifierContext reqPath, final ReadDataParams readParams) {
- final var queryParams = QueryParams.newQueryParameters(readParams, reqPath);
- final var fieldPaths = queryParams.fieldPaths();
- final var strategy = server.getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint());
- final NormalizedNode node;
- if (fieldPaths != null && !fieldPaths.isEmpty()) {
- node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
- readParams.withDefaults(), fieldPaths);
- } else {
- node = strategy.readData(readParams.content(), reqPath.getInstanceIdentifier(),
- readParams.withDefaults());
- }
- if (node == null) {
- throw new RestconfDocumentedException(
- "Request could not be completed because the relevant data model content does not exist",
- ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
- }
-
- return switch (readParams.content()) {
- case ALL, CONFIG -> {
- final QName type = node.name().getNodeType();
- yield Response.status(Status.OK)
- .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
- .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null) + "-"
- + type.getLocalName() + '"')
- .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
- .build();
+ private static void completeDataGET(final RestconfFuture<NormalizedNodePayload> future,
+ final ReadDataParams readParams, final AsyncResponse ar) {
+ future.addCallback(new JaxRsRestconfCallback<>(ar) {
+ @Override
+ Response transform(final NormalizedNodePayload result) {
+ return switch (readParams.content()) {
+ case ALL, CONFIG -> {
+ final var type = result.data().name().getNodeType();
+ yield Response.status(Status.OK)
+ .entity(result)
+ // FIXME: is this ETag okay?
+ .header("ETag", '"' + type.getModule().getRevision().map(Revision::toString).orElse(null)
+ + "-" + type.getLocalName() + '"')
+ .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
+ .build();
+ }
+ case NONCONFIG -> Response.status(Status.OK).entity(result).build();
+ };
}
- case NONCONFIG -> Response.status(Status.OK)
- .entity(new NormalizedNodePayload(reqPath.inference(), node, queryParams))
- .build();
- };
+ });
}
/**
* @param defaultsMode value of with-defaults parameter
* @return {@link NormalizedNode}
*/
+ // FIXME: NETCONF-1155: this method should asynchronous
public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
final @NonNull YangInstanceIdentifier path, final WithDefaultsParam defaultsMode) {
return switch (content) {
* @param fields paths to selected subtrees which should be read, relative to to the parent path
* @return {@link NormalizedNode}
*/
+ // FIXME: NETCONF-1155: this method should asynchronous
public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
final @NonNull List<YangInstanceIdentifier> fields) {
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfFuture;
+import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.server.spi.OperationOutput;
*/
@NonNullByDefault
public interface RestconfServer {
+ /**
+ * Return the content of the datastore.
+ *
+ * @param readParams {@link ReadDataParams} for this request
+ * @return A {@link RestconfFuture} of the {@link NormalizedNodePayload} content
+ */
+ RestconfFuture<NormalizedNodePayload> dataGET(ReadDataParams readParams);
+
+ /**
+ * Return the content of a resource.
+ *
+ * @param identifier resource identifier
+ * @param readParams {@link ReadDataParams} for this request
+ * @return A {@link RestconfFuture} of the {@link NormalizedNodePayload} content
+ */
+ RestconfFuture<NormalizedNodePayload> dataGET(String identifier, ReadDataParams readParams);
+
/**
* Return the set of supported RPCs supported by {@link #operationsPOST(URI, String, OperationInputBody)}.
*
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
-import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
.read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateFluentFuture(Optional.empty()))
.when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
- final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
- assertNotNull(response);
+
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(200, response.getStatus());
assertEquals(EMPTY_JUKEBOX, ((NormalizedNodePayload) response.getEntity()).data());
}
doReturn(immediateFluentFuture(Optional.of(wrapNodeByDataRootContainer(OPER_JUKEBOX))))
.when(read)
.read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.of());
- final Response response = dataService.readData(uriInfo);
- assertNotNull(response);
+
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.dataGET(uriInfo, asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(200, response.getStatus());
- final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
- assertTrue(data instanceof ContainerNode);
- final Collection<DataContainerChild> rootNodes = ((ContainerNode) data).body();
+ final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
+ final var rootNodes = data.body();
assertEquals(1, rootNodes.size());
- final Collection<DataContainerChild> allDataChildren = ((ContainerNode) rootNodes.iterator().next()).body();
+ final var allDataChildren = assertInstanceOf(ContainerNode.class, rootNodes.iterator().next()).body();
assertEquals(3, allDataChildren.size());
}
doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
- final Response response = dataService.readData(
- "example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo);
-
- assertNotNull(response);
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.dataGET("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox", uriInfo, asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(200, response.getStatus());
// response must contain all child nodes from config and operational containers merged in one container
- final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
- assertTrue(data instanceof ContainerNode);
- assertEquals(3, ((ContainerNode) data).size());
- assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
- assertNotNull(((ContainerNode) data).childByArg(LIBRARY_NID));
- assertNotNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
+ final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
+ assertEquals(3, data.size());
+ assertNotNull(data.childByArg(CONT_PLAYER.name()));
+ assertNotNull(data.childByArg(LIBRARY_NID));
+ assertNotNull(data.childByArg(PLAYLIST_NID));
}
@Test
doReturn(immediateFluentFuture(Optional.empty()))
.when(read).read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
- final var errors = assertThrows(RestconfDocumentedException.class,
- () -> dataService.readData("example-jukebox:jukebox", uriInfo)).getErrors();
+ final var rdeCaptor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
+ doReturn(true).when(asyncResponse).resume(rdeCaptor.capture());
+ dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
+
+ final var errors = rdeCaptor.getValue().getErrors();
assertEquals(1, errors.size());
final var error = errors.get(0);
assertEquals(ErrorType.PROTOCOL, error.getErrorType());
doReturn(immediateFluentFuture(Optional.of(CONFIG_JUKEBOX))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
- final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
-
- assertNotNull(response);
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(200, response.getStatus());
// response must contain only config data
- final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
+ final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
// config data present
- assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
- assertNotNull(((ContainerNode) data).childByArg(LIBRARY_NID));
+ assertNotNull(data.childByArg(CONT_PLAYER.name()));
+ assertNotNull(data.childByArg(LIBRARY_NID));
// state data absent
- assertNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
+ assertNull(data.childByArg(PLAYLIST_NID));
}
/**
doReturn(immediateFluentFuture(Optional.of(OPER_JUKEBOX))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, JUKEBOX_IID);
- final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.dataGET("example-jukebox:jukebox", uriInfo, asyncResponse);
+ final var response = responseCaptor.getValue();
- assertNotNull(response);
assertEquals(200, response.getStatus());
// response must contain only operational data
- final NormalizedNode data = ((NormalizedNodePayload) response.getEntity()).data();
+ final var data = assertInstanceOf(ContainerNode.class, ((NormalizedNodePayload) response.getEntity()).data());
// state data present
- assertNotNull(((ContainerNode) data).childByArg(CONT_PLAYER.name()));
- assertNotNull(((ContainerNode) data).childByArg(PLAYLIST_NID));
+ assertNotNull(data.childByArg(CONT_PLAYER.name()));
+ assertNotNull(data.childByArg(PLAYLIST_NID));
// config data absent
- assertNull(((ContainerNode) data).childByArg(LIBRARY_NID));
+ assertNull(data.childByArg(LIBRARY_NID));
}
@Test