import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableList;
import java.util.List;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.eclipse.jdt.annotation.NonNullByDefault;
-public class PatchContext {
- private final InstanceIdentifierContext context;
- private final List<PatchEntity> data;
- private final String patchId;
-
- public PatchContext(final InstanceIdentifierContext context, final List<PatchEntity> data, final String patchId) {
- this.context = requireNonNull(context);
- this.data = requireNonNull(data);
- this.patchId = requireNonNull(patchId);
- }
-
- public InstanceIdentifierContext getInstanceIdentifierContext() {
- return context;
- }
-
- public List<PatchEntity> getData() {
- return data;
+@NonNullByDefault
+public record PatchContext(String patchId, ImmutableList<PatchEntity> entities) {
+ public PatchContext {
+ requireNonNull(patchId);
+ requireNonNull(entities);
}
- public String getPatchId() {
- return patchId;
+ public PatchContext(final String patchId, final List<PatchEntity> entities) {
+ this(patchId, ImmutableList.copyOf(entities));
}
}
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-
package org.opendaylight.restconf.common.patch;
import static java.util.Objects.requireNonNull;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
* Holder of patch status context.
*/
public record PatchStatusContext(
+ // FIXME: DatabindContext when we are in our proper place
+ @NonNull EffectiveModelContext context,
@NonNull String patchId,
@NonNull List<PatchStatusEntity> editCollection,
boolean ok,
public PatchStatusContext {
requireNonNull(patchId);
+ requireNonNull(context);
requireNonNull(editCollection);
}
}
try (var jsonReader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
final var patchId = new AtomicReference<String>();
final var resultList = read(jsonReader, targetResource, patchId);
- return new PatchContext(targetResource, resultList, patchId.get());
+ // Note: patchId side-effect of above
+ return new PatchContext(patchId.get(), resultList);
}
}
private static @NonNull PatchContext parse(final InstanceIdentifierContext targetResource, final Document doc)
throws XMLStreamException, IOException, SAXException, URISyntaxException {
- final var resultCollection = new ArrayList<PatchEntity>();
+ final var entities = ImmutableList.<PatchEntity>builder();
final var patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
final var editNodes = doc.getElementsByTagName("edit");
final var schemaTree = DataSchemaContextTree.from(targetResource.getSchemaContext());
final var result = resultHolder.getResult().data();
// for lists allow to manipulate with list items through their parent
if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) {
- resultCollection.add(new PatchEntity(editId, oper, targetII.getParent(), result));
+ entities.add(new PatchEntity(editId, oper, targetII.getParent(), result));
} else {
- resultCollection.add(new PatchEntity(editId, oper, targetII, result));
+ entities.add(new PatchEntity(editId, oper, targetII, result));
}
} else {
- resultCollection.add(new PatchEntity(editId, oper, targetII));
+ entities.add(new PatchEntity(editId, oper, targetII));
}
}
- return new PatchContext(targetResource, ImmutableList.copyOf(resultCollection), patchId);
+ return new PatchContext(patchId, entities.build());
}
/**
import javax.ws.rs.ext.Provider;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@Provider
@Produces(MediaTypes.APPLICATION_YANG_DATA_JSON)
public class JsonPatchStatusBodyWriter extends AbstractPatchStatusBodyWriter {
@Override
public void writeTo(final PatchStatusContext patchStatusContext, final Class<?> type, final Type genericType,
- final Annotation[] annotations, final MediaType mediaType,
- final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
- throws IOException {
+ final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+ final OutputStream entityStream) throws IOException {
+ final var jsonWriter = createJsonWriter(entityStream);
+ jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status")
+ .beginObject().name("patch-id").value(patchStatusContext.patchId());
- final JsonWriter jsonWriter = createJsonWriter(entityStream);
- jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status");
- jsonWriter.beginObject();
- jsonWriter.name("patch-id").value(patchStatusContext.patchId());
if (patchStatusContext.ok()) {
reportSuccess(jsonWriter);
- } else {
- if (patchStatusContext.globalErrors() != null) {
- reportErrors(patchStatusContext.globalErrors(), jsonWriter);
- }
+ jsonWriter.endObject().endObject().flush();
+ }
+
+ final var context = patchStatusContext.context();
+ final var globalErrors = patchStatusContext.globalErrors();
+ if (globalErrors != null) {
+ reportErrors(context, globalErrors, jsonWriter);
+ }
- jsonWriter.name("edit-status");
- jsonWriter.beginObject();
- jsonWriter.name("edit");
- jsonWriter.beginArray();
- for (final PatchStatusEntity patchStatusEntity : patchStatusContext.editCollection()) {
- jsonWriter.beginObject();
- jsonWriter.name("edit-id").value(patchStatusEntity.getEditId());
- if (patchStatusEntity.getEditErrors() != null) {
- reportErrors(patchStatusEntity.getEditErrors(), jsonWriter);
- } else {
- if (patchStatusEntity.isOk()) {
- reportSuccess(jsonWriter);
- }
- }
- jsonWriter.endObject();
+ jsonWriter.name("edit-status").beginObject()
+ .name("edit").beginArray();
+ for (var editStatus : patchStatusContext.editCollection()) {
+ jsonWriter.beginObject().name("edit-id").value(editStatus.getEditId());
+
+ final var editErrors = editStatus.getEditErrors();
+ if (editErrors != null) {
+ reportErrors(context, editErrors, jsonWriter);
+ } else if (editStatus.isOk()) {
+ reportSuccess(jsonWriter);
}
- jsonWriter.endArray();
jsonWriter.endObject();
}
- jsonWriter.endObject();
- jsonWriter.endObject();
- jsonWriter.flush();
+ jsonWriter.endArray().endObject().endObject().endObject().flush();
}
private static void reportSuccess(final JsonWriter jsonWriter) throws IOException {
jsonWriter.name("ok").beginArray().nullValue().endArray();
}
- private static void reportErrors(final List<RestconfError> errors, final JsonWriter jsonWriter) throws IOException {
- jsonWriter.name("errors");
- jsonWriter.beginObject();
- jsonWriter.name("error");
- jsonWriter.beginArray();
+ private static void reportErrors(final EffectiveModelContext context, final List<RestconfError> errors,
+ final JsonWriter jsonWriter) throws IOException {
+ jsonWriter.name("errors").beginObject().name("error").beginArray();
- for (final RestconfError restconfError : errors) {
- jsonWriter.beginObject();
- jsonWriter.name("error-type").value(restconfError.getErrorType().elementBody());
- jsonWriter.name("error-tag").value(restconfError.getErrorTag().elementBody());
+ for (var restconfError : errors) {
+ jsonWriter.beginObject()
+ .name("error-type").value(restconfError.getErrorType().elementBody())
+ .name("error-tag").value(restconfError.getErrorTag().elementBody());
- // optional node
- if (restconfError.getErrorPath() != null) {
- jsonWriter.name("error-path").value(restconfError.getErrorPath().toString());
+ final var errorPath = restconfError.getErrorPath();
+ if (errorPath != null) {
+ // FIXME: YangInstanceIdentifier.toString(), we should use a codec based on context
+ jsonWriter.name("error-path").value(errorPath.toString());
}
-
- // optional node
- if (restconfError.getErrorMessage() != null) {
- jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+ final var errorMessage = restconfError.getErrorMessage();
+ if (errorMessage != null) {
+ jsonWriter.name("error-message").value(errorMessage);
}
-
- // optional node
- if (restconfError.getErrorInfo() != null) {
- jsonWriter.name("error-info").value(restconfError.getErrorInfo());
+ final var errorInfo = restconfError.getErrorInfo();
+ if (errorInfo != null) {
+ jsonWriter.name("error-info").value(errorInfo);
}
jsonWriter.endObject();
}
- jsonWriter.endArray();
- jsonWriter.endObject();
+ jsonWriter.endArray().endObject();
}
private static JsonWriter createJsonWriter(final OutputStream entityStream) {
final var tx = prepareWriteExecution();
boolean noError = true;
- for (var patchEntity : patch.getData()) {
+ for (var patchEntity : patch.entities()) {
if (noError) {
final var targetNode = patchEntity.getTargetNode();
final var editId = patchEntity.getEditId();
}
// if no errors then submit transaction, otherwise cancel
+ final var patchId = patch.patchId();
if (noError) {
try {
TransactionUtil.syncCommit(tx.commit(), "PATCH", null);
} catch (RestconfDocumentedException e) {
// if errors occurred during transaction commit then patch failed and global errors are reported
- return new PatchStatusContext(patch.getPatchId(), List.copyOf(editCollection), false, e.getErrors());
+ return new PatchStatusContext(context, patchId, List.copyOf(editCollection), false, e.getErrors());
}
- return new PatchStatusContext(patch.getPatchId(), List.copyOf(editCollection), true, null);
+ return new PatchStatusContext(context, patchId, List.copyOf(editCollection), true, null);
} else {
tx.cancel();
- return new PatchStatusContext(patch.getPatchId(), List.copyOf(editCollection), false, null);
+ return new PatchStatusContext(context, patchId, List.copyOf(editCollection), false, null);
}
}
import static java.util.Objects.requireNonNull;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
return null;
}
- final void checkPatchContext(final PatchContext patchContext) {
- assertNotNull(patchContext.getData());
-
- final var iid = patchContext.getInstanceIdentifierContext();
- assertNotNull(iid);
-
- assertNotNull(iid.getInstanceIdentifier());
- assertNotNull(iid.getSchemaContext());
- assertNotNull(iid.getSchemaNode());
- assertSame(mountPoint(), iid.getMountPoint());
+ static final void checkPatchContext(final PatchContext patchContext) {
+ assertNotNull(patchContext.patchId());
+ assertNotNull(patchContext.entities());
}
final @NonNull PatchContext parse(final String uriPath, final String patchBody) throws IOException {
}
}""");
checkPatchContext(returnValue);
- assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
+ assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
.build())
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
.withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
.withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
.withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
.withValue("data1")
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
.withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
.withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
}
.withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
.build())
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
.withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
.withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
.withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
.withValue("data1")
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
.withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
.withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
.build())
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
.withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
+ .build(), returnValue.entities().get(0).getNode());
}
/**
</edit>
</yang-patch>""");
checkPatchContext(returnValue);
- assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
+ assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.entities().get(0).getNode());
}
}
@Test
public void testPatchData() {
- final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, JUKEBOX_IID);
- final PatchContext patch = new PatchContext(iidContext, List.of(
+ final var patch = new PatchContext("test patch id", List.of(
new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
- new PatchEntity("delete data", Operation.Delete, GAP_IID)), "test patch id");
+ new PatchEntity("delete data", Operation.Delete, GAP_IID)));
doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doReturn(immediateFalseFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateTrueFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
- final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
+ final var status = dataService.yangPatchData(InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, JUKEBOX_IID),
+ patch);
assertTrue(status.ok());
assertEquals(3, status.editCollection().size());
assertEquals("replace data", status.editCollection().get(1).getEditId());
@Test
public void testPatchDataMountPoint() throws Exception {
- final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofMountPointPath(mountPoint,
- JUKEBOX_SCHEMA, JUKEBOX_IID);
- final PatchContext patch = new PatchContext(iidContext, List.of(
+ final var iidContext = InstanceIdentifierContext.ofMountPointPath(mountPoint, JUKEBOX_SCHEMA, JUKEBOX_IID);
+ final var patch = new PatchContext("test patch id", List.of(
new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
new PatchEntity("replace data", Operation.Replace, JUKEBOX_IID, EMPTY_JUKEBOX),
- new PatchEntity("delete data", Operation.Delete, GAP_IID)), "test patch id");
+ new PatchEntity("delete data", Operation.Delete, GAP_IID)));
doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doReturn(immediateFalseFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
- final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
+ final var status = dataService.yangPatchData(iidContext, patch);
assertTrue(status.ok());
assertEquals(3, status.editCollection().size());
assertNull(status.globalErrors());
@Test
public void testPatchDataDeleteNotExist() {
- final InstanceIdentifierContext iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, JUKEBOX_IID);
- final PatchContext patch = new PatchContext(iidContext, List.of(
+ final PatchContext patch = new PatchContext("test patch id", List.of(
new PatchEntity("create data", Operation.Create, JUKEBOX_IID, EMPTY_JUKEBOX),
new PatchEntity("remove data", Operation.Remove, GAP_IID),
- new PatchEntity("delete data", Operation.Delete, GAP_IID)), "test patch id");
+ new PatchEntity("delete data", Operation.Delete, GAP_IID)));
doNothing().when(readWrite).delete(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doReturn(immediateFalseFluentFuture())
doReturn(immediateFalseFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doReturn(true).when(readWrite).cancel();
+
+ final var iidContext = InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, JUKEBOX_IID);
final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
assertFalse(status.ok());
import org.junit.Test;
import org.mockito.Mock;
import org.opendaylight.restconf.api.query.ContentParam;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchEntity;
.build())
.build();
- patch(new PatchContext(
- InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, ARTIST_IID.node(NAME_QNAME)),
+ patch(new PatchContext("patchRMRm",
List.of(new PatchEntity("edit1", Operation.Replace, ARTIST_IID, buildArtistList),
new PatchEntity("edit2", Operation.Merge, ARTIST_IID, buildArtistList),
- new PatchEntity("edit3", Operation.Remove, ARTIST_IID)),
- "patchRMRm"), testPatchDataReplaceMergeAndRemoveStrategy(), false);
+ new PatchEntity("edit3", Operation.Remove, ARTIST_IID))),
+ testPatchDataReplaceMergeAndRemoveStrategy(), false);
}
abstract @NonNull RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy();
@Test
public final void testPatchDataCreateAndDelete() {
- patch(new PatchContext(InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, GAP_IID),
- List.of(new PatchEntity("edit1", Operation.Create, PLAYER_IID, EMPTY_JUKEBOX),
- new PatchEntity("edit2", Operation.Delete, CREATE_AND_DELETE_TARGET)),
- "patchCD"),
+ patch(new PatchContext("patchCD", List.of(
+ new PatchEntity("edit1", Operation.Create, PLAYER_IID, EMPTY_JUKEBOX),
+ new PatchEntity("edit2", Operation.Delete, CREATE_AND_DELETE_TARGET))),
testPatchDataCreateAndDeleteStrategy(), true);
}
@Test
public final void testPatchMergePutContainer() {
- patch(new PatchContext(InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, GAP_IID),
- List.of(new PatchEntity("edit1", Operation.Merge, PLAYER_IID, EMPTY_JUKEBOX)), "patchM"),
+ patch(new PatchContext("patchM", List.of(new PatchEntity("edit1", Operation.Merge, PLAYER_IID, EMPTY_JUKEBOX))),
testPatchMergePutContainerStrategy(), false);
}
@Test
public final void testDeleteNonexistentData() {
- final var patchStatusContext = deleteNonexistentDataTestStrategy().patchData(new PatchContext(
- InstanceIdentifierContext.ofLocalPath(JUKEBOX_SCHEMA, GAP_IID),
- List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET)), "patchD"), JUKEBOX_SCHEMA);
+ final var patchStatusContext = deleteNonexistentDataTestStrategy().patchData(new PatchContext("patchD",
+ List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))), JUKEBOX_SCHEMA);
assertFalse(patchStatusContext.ok());
}