*
* @param uriInfo request URI information
* @param body data node for put to config DS
- * @return {@link Response}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@PUT
@Path("/data")
MediaTypes.APPLICATION_YANG_DATA_JSON,
MediaType.APPLICATION_JSON,
})
- public Response putDataJSON(@Context final UriInfo uriInfo, final InputStream body) {
+ public void putDataJSON(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- return putData(null, uriInfo, jsonBody);
+ putData(null, uriInfo, jsonBody, ar);
}
}
* @param identifier path to target
* @param uriInfo request URI information
* @param body data node for put to config DS
- * @return {@link Response}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@PUT
@Path("/data/{identifier:.+}")
MediaTypes.APPLICATION_YANG_DATA_JSON,
MediaType.APPLICATION_JSON,
})
- public Response putDataJSON(@Encoded @PathParam("identifier") final String identifier,
- @Context final UriInfo uriInfo, final InputStream body) {
+ public void putDataJSON(@Encoded @PathParam("identifier") final String identifier,
+ @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var jsonBody = new JsonResourceBody(body)) {
- return putData(identifier, uriInfo, jsonBody);
+ putData(identifier, uriInfo, jsonBody, ar);
}
}
*
* @param uriInfo request URI information
* @param body data node for put to config DS
- * @return {@link Response}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@PUT
@Path("/data")
MediaType.APPLICATION_XML,
MediaType.TEXT_XML
})
- public Response putDataXML(@Context final UriInfo uriInfo, final InputStream body) {
+ public void putDataXML(@Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- return putData(null, uriInfo, xmlBody);
+ putData(null, uriInfo, xmlBody, ar);
}
}
* @param identifier path to target
* @param uriInfo request URI information
* @param body data node for put to config DS
- * @return {@link Response}
+ * @param ar {@link AsyncResponse} which needs to be completed
*/
@PUT
@Path("/data/{identifier:.+}")
MediaType.APPLICATION_XML,
MediaType.TEXT_XML
})
- public Response putDataXML(@Encoded @PathParam("identifier") final String identifier,
- @Context final UriInfo uriInfo, final InputStream body) {
+ public void putDataXML(@Encoded @PathParam("identifier") final String identifier,
+ @Context final UriInfo uriInfo, final InputStream body, @Suspended final AsyncResponse ar) {
try (var xmlBody = new XmlResourceBody(body)) {
- return putData(identifier, uriInfo, xmlBody);
+ putData(identifier, uriInfo, xmlBody, ar);
}
}
- private Response putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body) {
+ private void putData(final @Nullable String identifier, final UriInfo uriInfo, final ResourceBody body,
+ final AsyncResponse ar) {
final var reqPath = server.bindRequestPath(databindProvider.currentContext(), identifier);
final var insert = QueryParams.parseInsert(reqPath.getSchemaContext(), uriInfo);
final var req = bindResourceRequest(reqPath, body);
- return switch (
- req.strategy().putData(req.path(), req.data(), insert)) {
- // Note: no Location header, as it matches the request path
- case CREATED -> Response.status(Status.CREATED).build();
- case REPLACED -> Response.noContent().build();
- };
+ req.strategy().putData(req.path(), req.data(), insert).addCallback(new JaxRsRestconfCallback<>(ar,
+ status -> switch (status) {
+ // Note: no Location header, as it matches the request path
+ case CREATED -> Response.status(Status.CREATED).build();
+ case REPLACED -> Response.noContent().build();
+ }));
}
/**
* @param insert {@link Insert}
* @return A {@link CreateOrReplaceResult}
*/
- public final @NonNull CreateOrReplaceResult putData(final YangInstanceIdentifier path, final NormalizedNode data,
- final @Nullable Insert insert) {
+ public final RestconfFuture<CreateOrReplaceResult> putData(final YangInstanceIdentifier path,
+ final NormalizedNode data, final @Nullable Insert insert) {
final var exists = TransactionUtil.syncAccess(exists(path), path);
final ListenableFuture<? extends CommitInfo> commitFuture;
commitFuture = replaceAndCommit(prepareWriteExecution(), path, data);
}
- TransactionUtil.syncCommit(commitFuture, "PUT", path);
- return exists ? CreateOrReplaceResult.REPLACED : CreateOrReplaceResult.CREATED;
+ final var ret = new SettableRestconfFuture<CreateOrReplaceResult>();
+
+ Futures.addCallback(commitFuture, new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ ret.set(exists ? CreateOrReplaceResult.REPLACED : CreateOrReplaceResult.CREATED);
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ ret.setFailure(TransactionUtil.decodeException(cause, "PUT", path));
+ }
+ }, MoreExecutors.directExecutor());
+
+ return ret;
}
private ListenableFuture<? extends CommitInfo> insertAndCommitPut(final YangInstanceIdentifier path,
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
import org.opendaylight.netconf.api.DocumentedException;
import org.opendaylight.netconf.api.NetconfDocumentedException;
}
}
- /**
- * Synchronize commit future, translating any failure to a {@link RestconfDocumentedException}.
- *
- * @param future Commit future
- * @param txType Transaction type name
- * @param path Modified path
- * @throws RestconfDocumentedException if commit fails
- */
- static void syncCommit(final ListenableFuture<? extends CommitInfo> future, final String txType,
+ static @NonNull RestconfDocumentedException decodeException(final Throwable ex, final String txType,
final YangInstanceIdentifier path) {
- try {
- future.get();
- } catch (InterruptedException e) {
- LOG.warn("Transaction({}) FAILED!", txType, e);
- throw new RestconfDocumentedException("Transaction failed", e);
- } catch (ExecutionException e) {
- LOG.warn("Transaction({}) FAILED!", txType, e);
- throw decodeException(e, txType, path);
- }
- LOG.trace("Transaction({}) SUCCESSFUL", txType);
- }
-
- static @NonNull RestconfDocumentedException decodeException(final Throwable throwable,
- final String txType, final YangInstanceIdentifier path) {
- return decodeException(throwable, throwable, txType, path);
- }
-
- private static @NonNull RestconfDocumentedException decodeException(final ExecutionException ex,
- final String txType, final YangInstanceIdentifier path) {
- return decodeException(ex, ex.getCause(), txType, path);
- }
-
- private static @NonNull RestconfDocumentedException decodeException(final Throwable ex, final Throwable cause,
- final String txType, final YangInstanceIdentifier path) {
- if (cause instanceof TransactionCommitFailedException) {
+ if (ex instanceof TransactionCommitFailedException) {
// If device send some error message we want this message to get to client and not just to throw it away
// or override it with new generic message. We search for NetconfDocumentedException that was send from
// netconfSB and we create RestconfDocumentedException accordingly.
- for (var error : Throwables.getCausalChain(cause)) {
+ for (var error : Throwables.getCausalChain(ex)) {
if (error instanceof DocumentedException documentedError) {
final ErrorTag errorTag = documentedError.getErrorTag();
if (errorTag.equals(ErrorTag.DATA_EXISTS)) {
doReturn(immediateTrueFluentFuture()).when(read)
.exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
- final var response = dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
+
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.putDataJSON("example-jukebox:jukebox", uriInfo, stringInputStream("""
{
"example-jukebox:jukebox" : {
"player": {
"gap": "0.2"
}
}
- }"""));
- assertNotNull(response);
+ }"""), asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
doReturn(immediateTrueFluentFuture()).when(read)
.exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
- final var response = dataService.putDataXML("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox",
+
+ doReturn(true).when(asyncResponse).resume(responseCaptor.capture());
+ dataService.putDataXML("example-jukebox:jukebox/yang-ext:mount/example-jukebox:jukebox",
uriInfo, stringInputStream("""
<jukebox xmlns="http://example.com/ns/example-jukebox">
<player>
<gap>0.2</gap>
</player>
- </jukebox>"""));
- assertNotNull(response);
+ </jukebox>"""), asyncResponse);
+ final var response = responseCaptor.getValue();
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}