Skip to content

Commit

Permalink
delete existing metadata for delete commands;
Browse files Browse the repository at this point in the history
add unit tests;

Signed-off-by: Stefan Maute <stefan.maute@bosch.io>
  • Loading branch information
Stefan Maute committed Jun 7, 2022
1 parent 4c07157 commit 01117bf
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.eclipse.ditto.base.model.entity.metadata.Metadata;
import org.eclipse.ditto.base.model.entity.metadata.MetadataBuilder;
import org.eclipse.ditto.base.model.entity.metadata.MetadataModelFactory;
import org.eclipse.ditto.base.model.headers.DittoHeaderDefinition;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.signals.WithOptionalEntity;
Expand Down Expand Up @@ -120,7 +121,7 @@ protected Optional<Metadata> calculateRelativeMetadata(@Nullable final Thing ent
MetadataFromSignal.of(command, withOptionalEntity, existingRelativeMetadata);
return Optional.ofNullable(relativeMetadata.get());
} else if (command instanceof ThingModifyCommand<?> && !dittoHeaders.getMetadataFieldsToDelete().isEmpty()) {
return calculateMetadataForDeleteRequests(entity, command);
return calculateMetadataForDeleteMetadataRequests(entity, command);
}

return Optional.empty();
Expand Down Expand Up @@ -159,12 +160,12 @@ private Optional<Metadata> calculateMetadataForGetRequests(@Nullable Thing entit
return Optional.empty();
}

private Optional<Metadata> calculateMetadataForDeleteRequests(@Nullable Thing entity, final C command) {
private Optional<Metadata> calculateMetadataForDeleteMetadataRequests(@Nullable Thing entity, final C command) {
final Metadata existingMetadata = getExistingMetadata(entity, command);
final Set<JsonPointer> metadataFieldsToDelete = command.getDittoHeaders().getMetadataFieldsToDelete();
if (containsExactlySingleWildcard(metadataFieldsToDelete) && existingMetadata != null) {
// delete all metadata
return Optional.of(Metadata.newMetadata(JsonObject.empty()));
return Optional.of(MetadataModelFactory.emptyMetadata());
}

final Set<JsonPointer> metadataFieldsWithResolvedWildcard;
Expand All @@ -180,17 +181,21 @@ private Optional<Metadata> calculateMetadataForDeleteRequests(@Nullable Thing en
metadataFieldsWithResolvedWildcard = metadataFieldsToDelete;
}


if (existingMetadata != null && !metadataFieldsWithResolvedWildcard.isEmpty()) {
final MetadataBuilder metadataBuilder = existingMetadata.toBuilder();
metadataFieldsWithResolvedWildcard.forEach(metadataBuilder::remove);

return Optional.of(metadataBuilder.build());
return deleteMetadata(existingMetadata, metadataFieldsWithResolvedWildcard);
}

return Optional.empty();
}

private Optional<Metadata> deleteMetadata(final Metadata existingMetadata,
final Set<JsonPointer> metadataFieldsToDelete) {
final MetadataBuilder metadataBuilder = existingMetadata.toBuilder();
metadataFieldsToDelete.forEach(metadataBuilder::remove);

return Optional.of(metadataBuilder.build());
}

@Override
public boolean isDefined(final C command) {
return command instanceof ThingCommand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.base.model.entity.metadata.Metadata;
import org.eclipse.ditto.base.model.entity.metadata.MetadataBuilder;
import org.eclipse.ditto.base.model.signals.commands.Command;
import org.eclipse.ditto.internal.utils.persistentactors.events.EventStrategy;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.things.model.Thing;
import org.eclipse.ditto.things.model.ThingBuilder;
import org.eclipse.ditto.internal.utils.persistentactors.events.EventStrategy;
import org.eclipse.ditto.things.model.signals.events.ThingEvent;
import org.eclipse.ditto.things.model.signals.events.ThingModifiedEvent;

/**
* This abstract implementation of {@code EventStrategy} checks if the Thing to be handled is {@code null}.
Expand Down Expand Up @@ -64,7 +66,8 @@ protected Metadata mergeMetadata(@Nullable final Thing thing, final T event) {

final JsonPointer eventMetadataResourcePath = event.getResourcePath();
final Optional<Metadata> eventMetadataOpt = event.getMetadata();
final Optional<Metadata> thingMetadata = Optional.ofNullable(thing).flatMap(Thing::getMetadata);
final Optional<Metadata> thingMetadata = deleteMetadataForDeletedEvent(thing, event);

if (eventMetadataResourcePath.isEmpty() && eventMetadataOpt.isPresent()) {
return eventMetadataOpt.get();
} else if (eventMetadataOpt.isPresent()) {
Expand All @@ -91,4 +94,13 @@ protected ThingBuilder.FromCopy applyEvent(final T event, final ThingBuilder.Fro
return thingBuilder;
}

private Optional<Metadata> deleteMetadataForDeletedEvent(@Nullable final Thing thing, final T event) {
final Optional<Metadata> optionalMetadata = Optional.ofNullable(thing).flatMap(Thing::getMetadata);
// delete metadata for delete events
if (event instanceof ThingModifiedEvent && event.getCommandCategory().equals(Command.Category.DELETE)) {
return optionalMetadata.map(metadata -> metadata.toBuilder().remove(event.getResourcePath()).build());
} else {
return optionalMetadata;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,36 @@ public abstract class PersistenceActorTestBase {
Thing.JsonFields.CREATED, Thing.JsonFields.REVISION, Thing.JsonFields.POLICY_ID,
Thing.JsonFields.LIFECYCLE);

protected static final JsonFieldSelector ALL_FIELDS_SELECTOR_WITH_METADATA = JsonFactory.newFieldSelector(
Thing.JsonFields.ATTRIBUTES, Thing.JsonFields.FEATURES, Thing.JsonFields.ID, Thing.JsonFields.MODIFIED,
Thing.JsonFields.CREATED, Thing.JsonFields.REVISION, Thing.JsonFields.POLICY_ID,
Thing.JsonFields.LIFECYCLE, Thing.JsonFields.METADATA);

protected static final String FEATURE_ID = "featureId";
protected static final String FEATURE_KEY = "featureKey";
protected static final String FEATURE_VALUE = "featureValue";
protected static final Metadata METADATA = Metadata.newBuilder()
.set("attributes", JsonObject.newBuilder()
.set(ATTRIBUTE_KEY, JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.set("edited", "2022-05-31 15:55:55")
.build())
.build())
.set("features", JsonObject.newBuilder()
.set(FEATURE_ID, JsonObject.newBuilder()
.set("definition", JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.build())
.set("properties", JsonObject.newBuilder()
.set(FEATURE_KEY, JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.set("unit", "Quarks")
.build())
.build())
.build())
.build())
.build();

private static final FeatureProperties FEATURE_PROPERTIES =
FeatureProperties.newBuilder().set(FEATURE_KEY, FEATURE_VALUE).build();
private static final FeatureDefinition FEATURE_DEFINITION = FeatureDefinition.fromIdentifier("ns:name:version");
Expand Down Expand Up @@ -141,27 +168,7 @@ protected static Thing createThingV2WithRandomId() {
protected static Thing createThingV2WithRandomIdAndMetadata() {
return createThingV2WithId(ThingId.of(THING_ID.getNamespace(), THING_ID.getName() + UUID.randomUUID()))
.toBuilder()
.setMetadata(Metadata.newBuilder()
.set("attributes", JsonObject.newBuilder()
.set(ATTRIBUTE_KEY, JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.set("edited", "2022-05-31 15:55:55")
.build())
.build())
.set("features", JsonObject.newBuilder()
.set(FEATURE_ID, JsonObject.newBuilder()
.set("definition", JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.build())
.set("properties", JsonObject.newBuilder()
.set(FEATURE_KEY, JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.set("unit", "Quarks")
.build())
.build())
.build())
.build())
.build())
.setMetadata(METADATA)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@ public final class ThingPersistenceActorTest extends PersistenceActorTestBase {
JsonFactory.newParseOptionsBuilder().withoutUrlDecoding().build();

private static final Instant TIMESTAMP = Instant.EPOCH;
private static final Metadata METADATA = Metadata.newBuilder()
.set("creator", "The epic Ditto team")
.build();


private static void assertThingInResponse(final Thing actualThing, final Thing expectedThing) {
// Policy entries are ignored by things-persistence.
Expand Down Expand Up @@ -1517,6 +1513,112 @@ public void retrieveLeafMetadataWithGetMetadataWildcardHeader() {
}};
}

@Test
public void deleteAttributesMetadataWithDeleteMetadataHeader() {
final Thing thing = createThingV2WithRandomIdAndMetadata();
final DittoHeaders dittoHeaders = dittoHeadersV2.toBuilder()
.putHeader(DittoHeaderDefinition.DELETE_METADATA.getKey(), "attributes/")
.build();
final ThingCommand<?> modifyThingCommand = ModifyThing.of(getIdOrThrow(thing), thing, null, dittoHeaders);

new TestKit(actorSystem) {{
final ActorRef underTest = createPersistenceActorFor(thing);

// create thing from json to use initial metadata creation
final JsonObject commandJson = getJsonCommand(thing);
final CreateThing createThing = CreateThing.fromJson(commandJson, dittoHeadersV2);
underTest.tell(createThing, getRef());
expectMsgClass(CreateThingResponse.class);

underTest.tell(modifyThingCommand, getRef());
expectMsgClass(ModifyThingResponse.class);

// retrieve thing with metadata
final Metadata expectedMetadata = METADATA.toBuilder()
.remove("attributes")
.build();

final RetrieveThing retrieveModifiedThing =
RetrieveThing.getBuilder(getIdOrThrow(thing), dittoHeadersV2)
.withSelectedFields(ALL_FIELDS_SELECTOR_WITH_METADATA)
.build();
underTest.tell(retrieveModifiedThing, getRef());
final RetrieveThingResponse retrieveModifiedThingResponse = expectMsgClass(RetrieveThingResponse.class);
assertThat(retrieveModifiedThingResponse.getThing().getMetadata().orElseThrow())
.isEqualTo(expectedMetadata);
}};
}

@Test
public void deleteFeaturePropertyMetadataWithDeleteMetadataWildcardHeader() {
final Thing thing = createThingV2WithRandomIdAndMetadata();
final DittoHeaders dittoHeaders = dittoHeadersV2.toBuilder()
.putHeader(DittoHeaderDefinition.DELETE_METADATA.getKey(), "features/*/properties/*/unit")
.build();
final ThingCommand<?> modifyThingCommand = ModifyThing.of(getIdOrThrow(thing), thing, null, dittoHeaders);

new TestKit(actorSystem) {{
final ActorRef underTest = createPersistenceActorFor(thing);

// create thing from json to use initial metadata creation
final JsonObject commandJson = getJsonCommand(thing);
final CreateThing createThing = CreateThing.fromJson(commandJson, dittoHeadersV2);
underTest.tell(createThing, getRef());
expectMsgClass(CreateThingResponse.class);

underTest.tell(modifyThingCommand, getRef());
expectMsgClass(ModifyThingResponse.class);

// retrieve thing with metadata
final Metadata expectedMetadata = METADATA.toBuilder()
.remove("features/featureId/properties/featureKey/unit")
.build();

final RetrieveThing retrieveModifiedThing =
RetrieveThing.getBuilder(getIdOrThrow(thing), dittoHeadersV2)
.withSelectedFields(ALL_FIELDS_SELECTOR_WITH_METADATA)
.build();
underTest.tell(retrieveModifiedThing, getRef());
final RetrieveThingResponse retrieveModifiedThingResponse = expectMsgClass(RetrieveThingResponse.class);
assertThat(retrieveModifiedThingResponse.getThing().getMetadata().orElseThrow())
.isEqualTo(expectedMetadata);
}};
}

@Test
public void deleteMetadataForDeleteAttributeCommand() {
final Thing thing = createThingV2WithRandomIdAndMetadata();
final ThingCommand<?> deleteAttributeCommand =
DeleteAttribute.of(getIdOrThrow(thing), JsonPointer.of(ATTRIBUTE_KEY), dittoHeadersV2);

new TestKit(actorSystem) {{
final ActorRef underTest = createPersistenceActorFor(thing);

// create thing from json to use initial metadata creation
final JsonObject commandJson = getJsonCommand(thing);
final CreateThing createThing = CreateThing.fromJson(commandJson, dittoHeadersV2);
underTest.tell(createThing, getRef());
expectMsgClass(CreateThingResponse.class);

underTest.tell(deleteAttributeCommand, getRef());
expectMsgClass(DeleteAttributeResponse.class);

// retrieve thing with metadata
final Metadata expectedMetadata = METADATA.toBuilder()
.remove("attributes/attrKey")
.build();

final RetrieveThing retrieveModifiedThing =
RetrieveThing.getBuilder(getIdOrThrow(thing), dittoHeadersV2)
.withSelectedFields(ALL_FIELDS_SELECTOR_WITH_METADATA)
.build();
underTest.tell(retrieveModifiedThing, getRef());
final RetrieveThingResponse retrieveThingResponse = expectMsgClass(RetrieveThingResponse.class);
assertThat(retrieveThingResponse.getThing().getMetadata().orElseThrow())
.isEqualTo(expectedMetadata);
}};
}

private void assertPublishEvent(final ThingEvent<?> event) {
final ThingEvent<?> msg = pubSubTestProbe.expectMsgClass(ThingEvent.class);
Assertions.assertThat(msg.toJson())
Expand Down

0 comments on commit 01117bf

Please sign in to comment.