-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#559] implement conditional requests based on the condition in the d…
…itto headers; add AbstractConditionCheckingCommandStrategy which checks the specified condition against the actual thing state; add new exception ThingConditionFailedException; Signed-off-by: Stefan Maute <stefan.maute@bosch.io>
- Loading branch information
Stefan Maute
committed
Aug 18, 2021
1 parent
d7bdc11
commit bf49105
Showing
15 changed files
with
370 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
...o/internal/utils/persistentactors/condition/AbstractConditionCheckingCommandStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* Copyright (c) 2021 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.eclipse.ditto.internal.utils.persistentactors.condition; | ||
|
||
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull; | ||
|
||
import java.util.function.Predicate; | ||
|
||
import javax.annotation.Nullable; | ||
import javax.annotation.concurrent.Immutable; | ||
|
||
import org.eclipse.ditto.base.model.entity.Entity; | ||
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; | ||
import org.eclipse.ditto.base.model.headers.DittoHeaders; | ||
import org.eclipse.ditto.base.model.signals.commands.Command; | ||
import org.eclipse.ditto.base.model.signals.events.Event; | ||
import org.eclipse.ditto.internal.utils.persistentactors.etags.AbstractConditionHeaderCheckingCommandStrategy; | ||
import org.eclipse.ditto.internal.utils.persistentactors.results.Result; | ||
import org.eclipse.ditto.internal.utils.persistentactors.results.ResultFactory; | ||
import org.eclipse.ditto.rql.parser.RqlPredicateParser; | ||
import org.eclipse.ditto.rql.query.criteria.Criteria; | ||
import org.eclipse.ditto.rql.query.filter.QueryFilterCriteriaFactory; | ||
import org.eclipse.ditto.rql.query.things.ThingPredicateVisitor; | ||
import org.eclipse.ditto.things.model.Thing; | ||
import org.eclipse.ditto.things.model.signals.commands.ThingCommand; | ||
import org.eclipse.ditto.things.model.signals.commands.exceptions.ThingConditionFailedException; | ||
import org.eclipse.ditto.things.model.signals.commands.modify.CreateThing; | ||
|
||
/** | ||
* Responsible to check conditional requests based on the thing's current state and the specified condition header. | ||
* | ||
* @param <C> the type of the handled commands | ||
* @param <S> the type of the addressed entity | ||
* @param <K> the type of the context | ||
* @param <E> the type of the emitted events | ||
*/ | ||
@Immutable | ||
public abstract class AbstractConditionCheckingCommandStrategy< | ||
C extends Command<?>, | ||
S extends Entity<?>, | ||
K, | ||
E extends Event<?>> extends AbstractConditionHeaderCheckingCommandStrategy<C, S, K, E> { | ||
|
||
/** | ||
* Construct a command-strategy with condition header checking. | ||
* | ||
* @param theMatchingClass final class of the command to handle. | ||
*/ | ||
protected AbstractConditionCheckingCommandStrategy(final Class<C> theMatchingClass) { | ||
super(theMatchingClass); | ||
} | ||
|
||
/** | ||
* Checks condition header on the (sub-)entity determined by the given {@code command} and {@code thing}. | ||
* | ||
* @param context the context. | ||
* @param entity the entity, may be {@code null}. | ||
* @param nextRevision the next revision number of the entity. | ||
* @param command the command which addresses either the whole entity or a sub-entity | ||
* @return Either and error result if the specified condition does not meet the condition or the result of the | ||
* extending strategy. | ||
*/ | ||
@Override | ||
public Result<E> apply(final Context<K> context, @Nullable final S entity, final long nextRevision, | ||
final C command) { | ||
|
||
final String condition = command.getDittoHeaders().getCondition().orElse(null); | ||
|
||
if (condition != null && entity != null) { | ||
context.getLog().withCorrelationId(command) | ||
.debug("Validating condition <{}> on command <{}>.", condition, command); | ||
|
||
try { | ||
checkCondition((Thing) entity, command, condition); | ||
context.getLog().withCorrelationId(command) | ||
.debug("Validating condition succeeded."); | ||
} catch (final DittoRuntimeException dre) { | ||
context.getLog().withCorrelationId(command) | ||
.debug("Validating condition failed with exception <{}>.", dre.getMessage()); | ||
return ResultFactory.newErrorResult(dre, command); | ||
} | ||
} | ||
|
||
return super.apply(context, entity, nextRevision, command); | ||
} | ||
|
||
@Override | ||
public boolean isDefined(final Context<K> context, @Nullable final S entity, final C command) { | ||
checkNotNull(context, "Context"); | ||
checkNotNull(command, "Command"); | ||
|
||
return !(command instanceof CreateThing) && command instanceof ThingCommand && entity instanceof Thing; | ||
} | ||
|
||
private void checkCondition(final Thing entity, final C command, final String condition) { | ||
final DittoHeaders dittoHeaders = command.getDittoHeaders(); | ||
final Criteria criteria = QueryFilterCriteriaFactory.modelBased(RqlPredicateParser.getInstance()) | ||
.filterCriteria(condition, dittoHeaders); | ||
final Predicate<Thing> predicate = ThingPredicateVisitor.apply(criteria); | ||
if (!predicate.test(entity)) { | ||
throw ThingConditionFailedException.newBuilder(condition) | ||
.dittoHeaders(dittoHeaders) | ||
.build(); | ||
} | ||
} | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
...c/main/java/org/eclipse/ditto/internal/utils/persistentactors/condition/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* Copyright (c) 2021 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
|
||
/** | ||
* Entity-tag handlers. | ||
*/ | ||
@org.eclipse.ditto.utils.jsr305.annotations.AllValuesAreNonnullByDefault | ||
package org.eclipse.ditto.internal.utils.persistentactors.condition; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
...eclipse/ditto/things/model/signals/commands/exceptions/ThingConditionFailedException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright (c) 2021 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.eclipse.ditto.things.model.signals.commands.exceptions; | ||
|
||
import java.net.URI; | ||
import java.text.MessageFormat; | ||
|
||
import javax.annotation.Nullable; | ||
import javax.annotation.concurrent.Immutable; | ||
import javax.annotation.concurrent.NotThreadSafe; | ||
|
||
import org.eclipse.ditto.base.model.common.HttpStatus; | ||
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; | ||
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeExceptionBuilder; | ||
import org.eclipse.ditto.base.model.headers.DittoHeaders; | ||
import org.eclipse.ditto.base.model.json.JsonParsableException; | ||
import org.eclipse.ditto.json.JsonObject; | ||
import org.eclipse.ditto.things.model.ThingException; | ||
|
||
/** | ||
* Thrown when validating a condition on a Thing or one of its sub-entities is failing. | ||
*/ | ||
@Immutable | ||
@JsonParsableException(errorCode = ThingConditionFailedException.ERROR_CODE) | ||
public final class ThingConditionFailedException extends DittoRuntimeException implements ThingException { | ||
|
||
/** | ||
* Error code of this exception. | ||
*/ | ||
public static final String ERROR_CODE = ERROR_CODE_PREFIX + "condition.failed"; | ||
|
||
private static final String MESSAGE_TEMPLATE = | ||
"The specified condition ''{0}'' does not match the requested Thing."; | ||
|
||
private static final String DEFAULT_DESCRIPTION = "The condition provided in the condition header " + | ||
"evaluated to false for the requested Thing. Please check the value of your condition header value."; | ||
|
||
private ThingConditionFailedException(final DittoHeaders dittoHeaders, | ||
@Nullable final String message, | ||
@Nullable final String description, | ||
@Nullable final Throwable cause, | ||
@Nullable final URI href) { | ||
super(ERROR_CODE, HttpStatus.PRECONDITION_FAILED, dittoHeaders, message, description, cause, href); | ||
} | ||
|
||
/** | ||
* A mutable builder for a {@link org.eclipse.ditto.things.model.signals.commands.exceptions.ThingConditionFailedException}. | ||
* | ||
* @param condition the condition to apply for the request. | ||
* @return the builder. | ||
*/ | ||
public static Builder newBuilder(final String condition) { | ||
return new Builder(condition); | ||
} | ||
|
||
/** | ||
* Constructs a new {@link org.eclipse.ditto.things.model.signals.commands.exceptions.ThingConditionFailedException} | ||
* object with the exception message extracted from the given JSON object. | ||
* | ||
* @param jsonObject the JSON to read the {@link org.eclipse.ditto.base.model.exceptions.DittoRuntimeException.JsonFields#MESSAGE} field from. | ||
* @param dittoHeaders the headers of the command which resulted in this exception. | ||
* @return the new {@link org.eclipse.ditto.things.model.signals.commands.exceptions.ThingConditionFailedException}. | ||
* @throws NullPointerException if any argument is {@code null}. | ||
* @throws org.eclipse.ditto.json.JsonMissingFieldException if this JsonObject did not contain an error message. | ||
* @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected | ||
* format. | ||
*/ | ||
public static ThingConditionFailedException fromJson(final JsonObject jsonObject, | ||
final DittoHeaders dittoHeaders) { | ||
return DittoRuntimeException.fromJson(jsonObject, dittoHeaders, new Builder()); | ||
} | ||
|
||
@Override | ||
public DittoRuntimeException setDittoHeaders(final DittoHeaders dittoHeaders) { | ||
return new Builder() | ||
.message(getMessage()) | ||
.description(getDescription().orElse(null)) | ||
.cause(getCause()) | ||
.href(getHref().orElse(null)) | ||
.dittoHeaders(dittoHeaders) | ||
.build(); | ||
} | ||
|
||
/** | ||
* A mutable builder with a fluent API for a {@link org.eclipse.ditto.things.model.signals.commands.exceptions.ThingConditionFailedException}. | ||
*/ | ||
@NotThreadSafe | ||
public static final class Builder | ||
extends DittoRuntimeExceptionBuilder<ThingConditionFailedException> { | ||
|
||
private Builder() { | ||
description(DEFAULT_DESCRIPTION); | ||
} | ||
|
||
private Builder(final String condition) { | ||
this(); | ||
message(MessageFormat.format(MESSAGE_TEMPLATE, condition)); | ||
} | ||
|
||
@Override | ||
protected ThingConditionFailedException doBuild(final DittoHeaders dittoHeaders, | ||
@Nullable final String message, | ||
@Nullable final String description, | ||
@Nullable final Throwable cause, | ||
@Nullable final URI href) { | ||
return new ThingConditionFailedException(dittoHeaders, message, description, cause, href); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.