Skip to content

Commit

Permalink
fixed placeholder resolvment in "commandHeaders" of "ImplicitThingCre…
Browse files Browse the repository at this point in the history
…ation" mapper

* they were only resolved when the thingTemplate contained any placeholders
* also added additional placeholder resolvers for resolving "time:" and "request:subjectId" (useful for inline policy)
* added the missing documentation for the "ImplicitThingCreation" mapper options
* also added Time + Request placeholder resolvers to RawMessageMapper's header mapping

Signed-off-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
  • Loading branch information
thjaeckle committed Mar 2, 2022
1 parent 39aec5b commit ea9f0ba
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@
import static org.eclipse.ditto.base.model.exceptions.DittoJsonException.wrapJsonRuntimeException;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.BiFunction;

import javax.annotation.Nullable;

import org.eclipse.ditto.base.model.auth.AuthorizationContext;
import org.eclipse.ditto.base.model.common.Placeholders;
import org.eclipse.ditto.base.model.entity.id.NamespacedEntityIdInvalidException;
import org.eclipse.ditto.base.model.headers.DittoHeaderDefinition;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.headers.entitytag.EntityTagMatcher;
import org.eclipse.ditto.base.model.headers.entitytag.EntityTagMatchers;
import org.eclipse.ditto.base.model.signals.GlobalErrorRegistry;
import org.eclipse.ditto.base.model.signals.Signal;
import org.eclipse.ditto.connectivity.api.ExternalMessage;
import org.eclipse.ditto.connectivity.api.placeholders.ConnectivityPlaceholders;
import org.eclipse.ditto.connectivity.api.placeholders.RequestPlaceholder;
import org.eclipse.ditto.connectivity.model.MessageMapperConfigurationInvalidException;
import org.eclipse.ditto.connectivity.service.config.mapping.MappingConfig;
import org.eclipse.ditto.internal.utils.akka.logging.DittoLogger;
Expand All @@ -42,6 +46,7 @@
import org.eclipse.ditto.placeholders.HeadersPlaceholder;
import org.eclipse.ditto.placeholders.PlaceholderFactory;
import org.eclipse.ditto.placeholders.PlaceholderFilter;
import org.eclipse.ditto.placeholders.TimePlaceholder;
import org.eclipse.ditto.policies.model.Policy;
import org.eclipse.ditto.policies.model.PolicyId;
import org.eclipse.ditto.protocol.Adaptable;
Expand Down Expand Up @@ -72,16 +77,19 @@ public final class ImplicitThingCreationMessageMapper extends AbstractMessageMap
private static final DittoLogger LOGGER = DittoLoggerFactory.getLogger(ImplicitThingCreationMessageMapper.class);

private static final DittoProtocolAdapter DITTO_PROTOCOL_ADAPTER = DittoProtocolAdapter.newInstance();
private static final TimePlaceholder TIME_PLACEHOLDER = TimePlaceholder.getInstance();
private static final HeadersPlaceholder HEADERS_PLACEHOLDER = PlaceholderFactory.newHeadersPlaceholder();
private static final RequestPlaceholder REQUEST_PLACEHOLDER = ConnectivityPlaceholders.newRequestPlaceholder();
private static final String THING_TEMPLATE = "thing";
private static final String ALLOW_POLICY_LOCKOUT_OPTION = "allowPolicyLockout";
private static final String COMMAND_HEADERS = "commandHeaders";
private static final String THING_ID = "thingId";
private static final String THING_ID_CONFIGURATION_PROPERTY = THING_TEMPLATE + "/" + THING_ID;
private static final String POLICY_ID = "policyId";
private static final String POLICY_ID_CONFIGURATION_PROPERTY = THING_TEMPLATE + "/" + POLICY_ID;
public static final EntityTagMatcher ASTERISK = EntityTagMatcher.asterisk();

private final Function<Map<String, String>, ExpressionResolver> resolverFactory;
private final BiFunction<Map<String, String>, AuthorizationContext, ExpressionResolver> resolverFactory;

private String thingTemplate;
private Map<String, String> commandHeaders;
Expand All @@ -92,15 +100,16 @@ public final class ImplicitThingCreationMessageMapper extends AbstractMessageMap
*/
@SuppressWarnings("unused")
public ImplicitThingCreationMessageMapper() {
this(ImplicitThingCreationMessageMapper::getHeadersExpressionResolver);
this(ImplicitThingCreationMessageMapper::getExpressionResolver);
}

/**
* Constructor with customizable expression resolver.
*
* @param resolverFactory the creator of expression resolver.
*/
public ImplicitThingCreationMessageMapper(final Function<Map<String, String>, ExpressionResolver> resolverFactory) {
public ImplicitThingCreationMessageMapper(
final BiFunction<Map<String, String>, AuthorizationContext, ExpressionResolver> resolverFactory) {
this.resolverFactory = resolverFactory;
}

Expand All @@ -112,13 +121,16 @@ protected void doConfigure(final MappingConfig mappingConfig, final MessageMappe
commandHeaders = configuration.findProperty(COMMAND_HEADERS, JsonValue::isObject, JsonValue::asObject)
.filter(configuredHeaders -> !configuredHeaders.isEmpty())
.map(configuredHeaders -> {
final Map<String, String> newCommandHeaders = new HashMap<>();
final Map<String, String> newCommandHeaders = new LinkedHashMap<>();
newCommandHeaders.put(DittoHeaderDefinition.IF_NONE_MATCH.getKey(), ASTERISK.toString());
for (final JsonField field : configuredHeaders) {
newCommandHeaders.put(field.getKeyName(), field.getValue().formatAsString());
}
return Collections.unmodifiableMap(newCommandHeaders);
})
.orElse(Map.of());
.orElseGet(() -> DittoHeaders.newBuilder()
.ifNoneMatch(EntityTagMatchers.fromList(Collections.singletonList(ASTERISK)))
.build());

final JsonObject thingJson = JsonObject.of(thingTemplate);

Expand Down Expand Up @@ -170,7 +182,8 @@ public List<Adaptable> map(final ExternalMessage message) {
LOGGER.withCorrelationId(message.getInternalHeaders()).debug("Received ExternalMessage: {}", message);

final Map<String, String> externalHeaders = message.getHeaders();
final ExpressionResolver expressionResolver = resolverFactory.apply(externalHeaders);
final ExpressionResolver expressionResolver = resolverFactory.apply(externalHeaders,
message.getAuthorizationContext().orElse(null));

final String resolvedTemplate;
if (Placeholders.containsAnyPlaceholder(thingTemplate)) {
Expand All @@ -179,17 +192,14 @@ public List<Adaptable> map(final ExternalMessage message) {
resolvedTemplate = thingTemplate;
}

if (Placeholders.containsAnyPlaceholder(thingTemplate)) {
commandHeaders = resolveCommandHeaders(message, commandHeaders);
}
commandHeaders = resolveCommandHeaders(expressionResolver, commandHeaders);

final Signal<CreateThing> createThing = getCreateThingSignal(message, resolvedTemplate);
final Adaptable adaptable = DITTO_PROTOCOL_ADAPTER.toAdaptable(createThing);

// we cannot set the header on CreateThing directly because it is filtered when mapped to an adaptable
final DittoHeaders modifiedHeaders = adaptable.getDittoHeaders().toBuilder()
final DittoHeaders modifiedHeaders = DittoHeaders.of(commandHeaders).toBuilder()
.allowPolicyLockout(allowPolicyLockout)
.ifNoneMatch(EntityTagMatchers.fromList(Collections.singletonList(EntityTagMatcher.asterisk())))
.build();
final Adaptable adaptableWithModifiedHeaders = adaptable.setDittoHeaders(modifiedHeaders);

Expand All @@ -199,9 +209,13 @@ public List<Adaptable> map(final ExternalMessage message) {
return Collections.singletonList(adaptableWithModifiedHeaders);
}

private static ExpressionResolver getHeadersExpressionResolver(final Map<String, String> headers) {
private static ExpressionResolver getExpressionResolver(final Map<String, String> headers,
@Nullable final AuthorizationContext authorizationContext) {
return PlaceholderFactory.newExpressionResolver(
PlaceholderFactory.newPlaceholderResolver(HEADERS_PLACEHOLDER, headers));
PlaceholderFactory.newPlaceholderResolver(TIME_PLACEHOLDER, new Object()),
PlaceholderFactory.newPlaceholderResolver(HEADERS_PLACEHOLDER, headers),
PlaceholderFactory.newPlaceholderResolver(REQUEST_PLACEHOLDER, authorizationContext)
);
}

private static String applyPlaceholderReplacement(final String template, final ExpressionResolver resolver) {
Expand All @@ -219,17 +233,14 @@ private Signal<CreateThing> getCreateThingSignal(final ExternalMessage message,
return CreateThing.of(newThing, inlinePolicyJson, copyPolicyFrom, dittoHeaders);
}

private Map<String, String> resolveCommandHeaders(final ExternalMessage externalMessage,
final Map<String, String> errorResponseHeaders) {
final ExpressionResolver resolver = resolverFactory.apply(externalMessage.getHeaders());
final Map<String, String> resolvedHeaders = new HashMap<>();
errorResponseHeaders.forEach((key, value) ->
private static Map<String, String> resolveCommandHeaders(final ExpressionResolver resolver,
final Map<String, String> unresolvedHeaders) {
final Map<String, String> resolvedHeaders = new LinkedHashMap<>();
unresolvedHeaders.forEach((key, value) ->
resolver.resolve(value).toOptional().ifPresent(resolvedHeaderValue ->
resolvedHeaders.put(key, resolvedHeaderValue)
)
);
// throws IllegalArgumentException or SubjectInvalidException
// if resolved headers are missing mandatory headers or the resolved subject is invalid.
return resolvedHeaders;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.eclipse.ditto.connectivity.api.ExternalMessage;
import org.eclipse.ditto.connectivity.api.ExternalMessageBuilder;
import org.eclipse.ditto.connectivity.api.ExternalMessageFactory;
import org.eclipse.ditto.connectivity.api.placeholders.ConnectivityPlaceholders;
import org.eclipse.ditto.connectivity.api.placeholders.RequestPlaceholder;
import org.eclipse.ditto.connectivity.service.config.mapping.MappingConfig;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
Expand All @@ -45,7 +47,9 @@
import org.eclipse.ditto.messages.model.MessagesModelFactory;
import org.eclipse.ditto.messages.model.signals.commands.MessageDeserializer;
import org.eclipse.ditto.placeholders.ExpressionResolver;
import org.eclipse.ditto.placeholders.HeadersPlaceholder;
import org.eclipse.ditto.placeholders.PlaceholderFactory;
import org.eclipse.ditto.placeholders.TimePlaceholder;
import org.eclipse.ditto.protocol.Adaptable;
import org.eclipse.ditto.protocol.MessagePath;
import org.eclipse.ditto.protocol.Payload;
Expand Down Expand Up @@ -75,6 +79,10 @@ public final class RawMessageMapper extends AbstractMessageMapper {
private static final ContentType DEFAULT_OUTGOING_CONTENT_TYPE =
ContentType.of(ContentTypes.TEXT_PLAIN_UTF8.toString());

private static final TimePlaceholder TIME_PLACEHOLDER = TimePlaceholder.getInstance();
private static final HeadersPlaceholder HEADERS_PLACEHOLDER = PlaceholderFactory.newHeadersPlaceholder();
private static final RequestPlaceholder REQUEST_PLACEHOLDER = ConnectivityPlaceholders.newRequestPlaceholder();

/**
* Default incoming content type is binary.
*/
Expand Down Expand Up @@ -236,9 +244,12 @@ private static Map<String, String> evaluateOutgoingMessageHeaders(final Adaptabl

private static Optional<MessageHeaders> evaluateIncomingMessageHeaders(final ExternalMessage externalMessage,
final Map<String, String> incomingMessageHeaders) {
final ExpressionResolver resolver =
PlaceholderFactory.newExpressionResolver(PlaceholderFactory.newHeadersPlaceholder(),
externalMessage.getHeaders());
final ExpressionResolver resolver = PlaceholderFactory.newExpressionResolver(
PlaceholderFactory.newPlaceholderResolver(TIME_PLACEHOLDER, new Object()),
PlaceholderFactory.newPlaceholderResolver(HEADERS_PLACEHOLDER, externalMessage.getHeaders()),
PlaceholderFactory.newPlaceholderResolver(REQUEST_PLACEHOLDER,
externalMessage.getAuthorizationContext().orElse(null))
);
final String contentTypeKey = DittoHeaderDefinition.CONTENT_TYPE.getKey();
final String contentType = resolve(resolver, incomingMessageHeaders, contentTypeKey);
if (contentType == null) {
Expand Down
Loading

0 comments on commit ea9f0ba

Please sign in to comment.