Skip to content

Commit

Permalink
keep order of metadata when creating it;
Browse files Browse the repository at this point in the history
remove metadata when attributes or features are modified and parts of it are removed;
add unit test;

Signed-off-by: Stefan Maute <stefan.maute@bosch.io>
  • Loading branch information
Stefan Maute committed Jun 24, 2022
1 parent 0e0efab commit bc72e82
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,12 @@ private static Set<JsonPointer> getReplacedWildcardsPointersForLeafs(final List<
metadataKey.asPointer())));

featureIds.forEach(featureId -> {
if (isFeatureDefinitionPresent(thing, featureId)) {
final JsonPointer jsonPointer =
getReplacedWildcardPointerForDefinitionLevel(featureId, metadataKey.asPointer());
replacedWildcardsPointers.add(jsonPointer);
}

replacedWildcardsPointers.addAll(
getReplacedWildcardPointersForPropertyKeysOnFeaturesLevel(
getFeaturePropertyLeafsFromThing(thing, featureId), JsonPointer.of(featureId),
Expand All @@ -496,12 +502,6 @@ private static Set<JsonPointer> getReplacedWildcardsPointersForLeafs(final List<
getReplacedWildcardPointersForPropertyKeysOnFeaturesLevel(
getFeatureDesiredPropertyLeafsFromThing(thing, featureId), JsonPointer.of(featureId),
DESIRED_PROPERTIES_POINTER, metadataKey.asPointer()));

if (isFeatureDefinitionPresent(thing, featureId)) {
final JsonPointer jsonPointer =
getReplacedWildcardPointerForDefinitionLevel(featureId, metadataKey.asPointer());
replacedWildcardsPointers.add(jsonPointer);
}
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -120,7 +121,7 @@ public Metadata get() {
if (!metadataHeaders.isEmpty()) {
final var expandedMetadataHeaders = metadataHeaders.stream()
.flatMap(this::expandWildcards)
.collect(Collectors.toSet());
.collect(Collectors.toCollection(LinkedHashSet::new));
return buildMetadata(commandEntity, expandedMetadataHeaders);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
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.JsonFactory;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.things.model.Thing;
import org.eclipse.ditto.things.model.ThingBuilder;
import org.eclipse.ditto.things.model.signals.events.ThingEvent;
Expand Down Expand Up @@ -61,31 +63,31 @@ public Thing handle(final T event, @Nullable final Thing thing, final long revis
return null;
}

@Nullable
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 = deleteMetadataForDeletedEvent(thing, event);
final Optional<Metadata> thingMetadata = Optional.ofNullable(thing).flatMap(Thing::getMetadata);
final MetadataBuilder metadataBuilder =
thingMetadata.map(Metadata::toBuilder).orElseGet(Metadata::newBuilder);

if (eventMetadataResourcePath.isEmpty() && eventMetadataOpt.isPresent()) {
return eventMetadataOpt.get();
} else if (eventMetadataOpt.isPresent()) {
final Metadata eventMetadata = eventMetadataOpt.get();
final MetadataBuilder metadataBuilder =
thingMetadata.map(Metadata::toBuilder).orElseGet(Metadata::newBuilder);
metadataBuilder.set(eventMetadataResourcePath, eventMetadata.toJson());
return metadataBuilder.build();

return deleteMetadataForModifiedEvents(event,
metadataBuilder.set(eventMetadataResourcePath, eventMetadata.toJson()));
} else {
return thingMetadata.orElse(null);
return deleteMetadataForModifiedEvents(event, metadataBuilder);
}
}

/**
* Apply the specified event to the also specified ThingBuilder.
* The builder has already the specified revision set as well as the event's timestamp.
*
* @param event the ThingEvent<?>to be applied.
* @param event the ThingEvent<?> to be applied.
* @param thingBuilder builder which is derived from the {@code event}'s Thing with the revision and event
* timestamp already set.
* @return the updated {@code thingBuilder} after applying {@code event}.
Expand All @@ -94,13 +96,24 @@ 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
private Metadata deleteMetadataForModifiedEvents(final T event, final MetadataBuilder metadataBuilder) {
if (event instanceof ThingModifiedEvent && event.getCommandCategory().equals(Command.Category.DELETE)) {
return optionalMetadata.map(metadata -> metadata.toBuilder().remove(event.getResourcePath()).build());
} else {
return optionalMetadata;
return metadataBuilder.remove(event.getResourcePath()).build();
}
else if (event instanceof ThingModifiedEvent && event.getCommandCategory().equals(Command.Category.MERGE)) {
final Optional<JsonValue> optionalJsonValue = event.getEntity();
if (optionalJsonValue.isEmpty() || optionalJsonValue.get().isNull()) {
return metadataBuilder.remove(event.getResourcePath()).build();
}
} else if (event instanceof ThingModifiedEvent && event.getCommandCategory().equals(Command.Category.MODIFY)) {
final Optional<JsonValue> optionalJsonValue = event.getEntity();
if (optionalJsonValue.isPresent() && optionalJsonValue.get().isObject()
&& optionalJsonValue.get().asObject().isEmpty()) {
return metadataBuilder.set(event.getResourcePath(), JsonFactory.newObject()).build();
}
}

return metadataBuilder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
import org.eclipse.ditto.things.model.signals.commands.modify.DeleteAttributeResponse;
import org.eclipse.ditto.things.model.signals.commands.modify.DeleteThing;
import org.eclipse.ditto.things.model.signals.commands.modify.DeleteThingResponse;
import org.eclipse.ditto.things.model.signals.commands.modify.MergeThing;
import org.eclipse.ditto.things.model.signals.commands.modify.MergeThingResponse;
import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttribute;
import org.eclipse.ditto.things.model.signals.commands.modify.ModifyAttributes;
import org.eclipse.ditto.things.model.signals.commands.modify.ModifyFeature;
Expand Down Expand Up @@ -1681,14 +1683,15 @@ public void retrieveLeafMetadataWithGetMetadataWildcardHeader() {
.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_PROPERTY_KEY, JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.build())
.build())
.set("definition", JsonObject.newBuilder()
.set("issuedBy", "the epic Ditto team")
.build())
.build())
.build())
.build();
Expand Down Expand Up @@ -1773,6 +1776,79 @@ public void deleteFeaturePropertyMetadataWithDeleteMetadataWildcardHeader() {
}};
}

@Test
public void deleteFeaturePropertyMetadataWithDeleteMetadataWildcardHeaderForMergeCommand() {
final Thing thing = createThingV2WithRandomIdAndMetadata();
final DittoHeaders dittoHeaders = dittoHeadersV2.toBuilder()
.putHeader(DittoHeaderDefinition.DELETE_METADATA.getKey(), "/unit")
.build();
final ThingCommand<?> mergeThingCommand =
MergeThing.withFeatureProperty(getIdOrThrow(thing), FEATURE_ID, JsonPointer.of(FEATURE_PROPERTY_KEY),
JsonFactory.newValue("bumlux"), 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(mergeThingCommand, getRef());
expectMsgClass(MergeThingResponse.class);

// retrieve thing with metadata
final Metadata expectedMetadata = METADATA.toBuilder()
.remove("features/featureId/properties/featurePropertyKey/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 deleteExistingMetadataWithMergeCommand() {
final Thing thing = createThingV2WithRandomIdAndMetadata();
final ThingCommand<?> mergeThingCommand =
MergeThing.withFeatureProperty(getIdOrThrow(thing), FEATURE_ID, JsonPointer.of(FEATURE_PROPERTY_KEY),
JsonFactory.nullLiteral(), 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(mergeThingCommand, getRef());
expectMsgClass(MergeThingResponse.class);

// retrieve thing with metadata
final Metadata expectedMetadata = METADATA.toBuilder()
.remove("features/featureId/properties/featurePropertyKey")
.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();
Expand Down

0 comments on commit bc72e82

Please sign in to comment.