com.pushtechnology.diffusion.client.features.UpdateConstraint Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2018, 2023 DiffusionData Ltd., All Rights Reserved.
*
* Use is subject to licence terms.
*
* NOTICE: All information contained herein is, and remains the
* property of DiffusionData. The intellectual and technical
* concepts contained herein are proprietary to DiffusionData and
* may be covered by U.S. and Foreign Patents, patents in process, and
* are protected by trade secret or copyright law.
*******************************************************************************/
package com.pushtechnology.diffusion.client.features;
import static com.pushtechnology.diffusion.client.features.UpdateConstraint.Operator.IS;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.Bytes;
import com.pushtechnology.diffusion.datatype.DataTypes;
import com.pushtechnology.diffusion.datatype.json.JSON;
/**
* A constraint to be applied to an update operation or the creation of an
* update stream.
*
* Constraints describe a condition that must be satisfied for an operation to
* succeed. Constraints can be applied to the setting of a value or creation of
* an update stream. Constraints are only evaluated on the server.
*
* The constraints are evaluated using the:
*
* - active session locks
*
- existence of the topic
*
- current value of the topic
*
*
* The value of a topic can be described in several ways. The value can be
* described as an exact value, a partial value or an unset value.
*
* Constraints can be composed with one another using logical ANDs or ORs. It is
* possible to compose a constraint that can never be satisfied although certain
* combinations, such as ANDing two incompatible constraints are prevented.
* Multiple session locks can be held but a topic can only have a single value.
*
* Constraints can be created using a {@link UpdateConstraint.Factory}, an
* instance of which can be obtained using
* {@link com.pushtechnology.diffusion.client.Diffusion#updateConstraints update
* constraints}. For example:
*
*
*
* UpdateConstraint.Factory factory = Diffusion.updateConstraints();
* UpdateConstraint constraint = factory.locked(lock).and(factory.value(EQ, expectedValue));
*
*
*
* @author DiffusionData Limited
* @since 6.2
*/
public interface UpdateConstraint {
/**
* An operator used in a constraint comparison.
*
* @since 6.10
*/
enum Operator {
/**
* Strict binary equality.
*
* This operator requires that the binary topic value is exactly
* equivalent to the value supplied for comparison.
*/
IS,
/**
* Lenient equals.
*
* This operator requires that the topic value is logically equal to the
* supplied value.
*
* If the supplied value is a string the string representation of the
* specified topic value is compared for string equality.
*
* If the supplied value is a number ({@link Long} or {@link Double})
* the corresponding topic value may be a number or a string containing
* a parseable number and will be compared for numeric equality.
*
* If the supplied value is null the condition will be satisfied if the
* value at a specified pointer is JSON null.
*/
EQ,
/**
* Lenient not equals.
*
* This operator requires that the topic value is logically not equal to
* the supplied value.
*
* If the supplied value is a string the string representation of the
* specified topic value is compared for string equality.
*
* If the supplied value is a number ({@link Long} or {@link Double})
* the corresponding topic value may be a number or a string containing
* a parseable number and will be compared for numeric equality.
*
* If the supplied value is null the condition will be satisfied if the
* value at a specified pointer not JSON null.
*/
NE,
/**
* Lenient greater than.
*
* This operator requires that the topic value is greater than the
* supplied value.
*
* The supplied value must be a number ({@link Long} or {@link Double}).
* The corresponding topic value may be a number or a string containing
* a parseable number and the condition will be satisfied if the topic
* value is greater than the supplied value.
*/
GT,
/**
* Lenient greater than or equals.
*
* This operator requires that the topic value is greater than or equal
* to the supplied value.
*
* The supplied value must be a number ({@link Long} or {@link Double}).
* The corresponding topic value may be a number or a string containing
* a parseable number and the condition will be satisfied if the topic
* value is greater than or equal to the supplied value.
*/
GE,
/**
* Lenient less than.
*
* This operator requires that the topic value is less than the supplied
* value.
*
* The supplied value must be a number ({@link Long} or {@link Double}).
* The corresponding topic value may be a number or a string containing
* a parseable number and the condition will be satisfied if the topic
* value is less than the supplied value.
*/
LT,
/**
* Lenient less than or equals.
*
* This operator requires that the topic value is less than or equal to
* the supplied value.
*
* The supplied value must be a number ({@link Long} or {@link Double}).
* The corresponding topic value may be a number or a string containing
* a parseable number and the condition will be satisfied if the topic
* value is less than or equal to the supplied value.
*/
LE;
}
/**
* Returns a composed constraint that represents a logical AND of this
* constraint and another.
*
* @param other a constraint that will be logically-ANDed with this
* constraint
* @return a composed constraint that represents a logical AND of this
* constraint and the {@code other} constraint
* @throws IllegalArgumentException if the composed constraint would be
* unsatisfiable
*/
UpdateConstraint and(UpdateConstraint other);
/**
* Returns a composed constraint that represents a logical OR of this
* constraint and another.
*
* @param other a constraint that will be logically-ORed with this
* constraint
* @return a composed constraint that represents a logical OR of this
* constraint and the {@code other} constraint
* @since 6.10
*/
UpdateConstraint or(UpdateConstraint other);
/**
* A constraint requiring the current value of a {@link TopicType#JSON JSON}
* topic to match the partially described value.
*
* The code:
*
*
*
* Constraint.Factory factory = Diffusion.updateConstraints();
* PartialJSON constraint =
* factory.jsonValue()
* .with("/id", String.class, idValue)
* .without("/cancellation");
*
*
*
* creates a constraint for a JSON object with a specific ID value and no
* value for a "cancellation" property.
*
* Missing keys are matched differently to keys that are present with null
* values.
*/
interface PartialJSON extends UpdateConstraint {
/**
* Require a value at a specific position in the JSON object.
*
* This is equivalent to calling {@link #with(String, Operator, Object)}
* with an operator of {@link Operator#IS IS}.
*
* @param pointer the pointer expression
*
* @param valueClass this parameter is no longer used and is ignored
*
* @param value the value
*
* @param the value type of the value at the pointer
*
* @return a new constraint
*
* @throws IllegalArgumentException if the {@code pointer} parameter
* cannot be parsed as a JSON pointer or if the class of the
* supplied value is not supported
*
* @deprecated since 6.10
*
* Rather use the {@link #with(String, Operator, Object)}
* method with the {@link Operator#IS IS} operator.
*/
@Deprecated
PartialJSON with(String pointer, Class valueClass, V value);
/**
* Compares a location within the JSON topic value to a specified value.
*
* If there is no value found at the specified pointer position, the
* constraint will be unsatisfied.
*
* If a {@link String} value is supplied and the operator is
* {@link Operator#EQ EQ} or {@link Operator#NE NE}, the string
* representation of the topic value at the given pointer will be
* compared to the supplied value. If the value at the pointer position
* is not a string or number the constraint will be unsatisfied. Other
* operators (other than {@link Operator#IS IS}) are not permitted with
* String values.
*
* If a number value ({@link Integer}, {@link Long} or {@link Double})
* is supplied the value will be compared with the number value at the
* topic location. This will work with JSON string or number values
* only. JSON strings can only be compared if they contain a value that
* can be parsed as a number. If a string value at the location cannot
* be parsed as a number, the constraint will be unsatisfied. Any of the
* operators (other than {@link Operator#IS IS}) can be used with such
* number comparisons. Decimal numbers can be compared with integral
* numbers so {@code 1} is equal to {@code 1.0}, {@code "1"}, or
* {@code "1.0"}.
*
* If a {@code null} value is supplied and the operator is
* {@link Operator#EQ EQ} or {@link Operator#NE NE}, the topic value at
* the given pointer will be compared to JSON null. Other operators
* (other than {@link Operator#IS IS}) are not permitted with a
* {@code null} value.
*
* If a {@link Boolean} value is supplied and the operator is
* {@link Operator#EQ EQ}, the topic value at the given pointer will be
* compared to the boolean value. Other operators are not permitted with
* a boolean value.
*
* If the {@link Operator#IS IS} operator is specified the supplied
* value will be compared to the topic value for strict binary equality.
* In this case the value must be of type {@link String},
* {@link Integer}, {@link Long}, {@link Double}, {@link Bytes}, or
* {@code null}. This is slightly more efficient than the lenient
* comparisons described above.
*
* @param pointer a JSON
* Pointer specifying the location of the {@code value} in
* the JSON object.
*
* @param operator the operator that determines the type of comparison
*
* @param value the value
*
* @return a new constraint
*
* @throws IllegalArgumentException if the {@code pointer} parameter
* cannot be parsed as a JSON pointer, the {@code value} type is
* not supported, or the operator is not compatible with the
* {@code value} type
*
* @since 6.10
*/
PartialJSON with(
String pointer,
Operator operator,
Object value);
/**
* Require a specific position in the JSON object to be absent. This
* does not match positions that have null values.
*
* The {@code pointer} is a
* JSON Pointer syntax
* reference that should have no value in the JSON object.
*
* @param pointer the pointer expression
* @return a new constraint
* @throws IllegalArgumentException if the {@code pointer} parameter
* cannot be parsed as a JSON pointer
*/
PartialJSON without(String pointer);
}
/**
* Factory for the constraint types.
*
* An instance can be obtained by calling
* {@link com.pushtechnology.diffusion.client.Diffusion#updateConstraints()
* Diffusion.updateConstraints()}.
*/
interface Factory {
/**
* Create a constraint requiring a lock to be held by the session.
*
* This can be used to coordinate operations between multiple sessions.
*
* @param lock the lock
* @return the constraint
*/
UpdateConstraint locked(Session.SessionLock lock);
/**
* Create a constraint requiring the current value of a topic to exactly
* match the supplied value.
*
* This is exactly equivalent to calling
* {@link #value(UpdateConstraint.Operator, Object)} specifying the
* {@link UpdateConstraint.Operator#IS IS} operator.
*
* @param value the value
*
* @return the constraint
*
* @throws IllegalArgumentException if the value type is not supported
*/
default UpdateConstraint value(Object value)
throws IllegalArgumentException {
return value(IS, value);
}
/**
* Create a constraint comparing the current value of a topic to a
* supplied value.
*
* If a {@link String} value is supplied and the operator is
* {@link UpdateConstraint.Operator#EQ EQ} or
* {@link UpdateConstraint.Operator#NE NE}, the string representation of
* the topic will be compared to the supplied value. This can only be
* used with primitive topic types (or {@link TopicType#TIME_SERIES
* TIME_SERIES} topics with a primitive event type). Other operators
* (other than {@link UpdateConstraint.Operator#IS IS}) are not
* permitted with String values.
*
* If a number value is supplied ({@link Integer}, {@link Long} or
* {@link Double}) the value will be compared with the number value of
* the topic. This will work with {@link TopicType#STRING STRING},
* {@link TopicType#INT64 INT64} or {@link TopicType#DOUBLE DOUBLE}
* topics (or {@link TopicType#TIME_SERIES TIME_SERIES} topics with a
* primitive event type) only. {@link TopicType#STRING STRING} topics
* can only be compared if they contain a value that can be parsed as a
* number. If the value of a {@link TopicType#STRING STRING} topic
* cannot be parsed as a number, or the topic is of any other non-number
* type the constraint will be unsatisfied. Any of the operators (other
* than {@link UpdateConstraint.Operator#IS IS}) can be used with such
* number comparisons. Decimal numbers can be compared with integral
* numbers so {@code 1} is equal to {@code 1.0}, {@code "1"}, or
* {@code "1.0"}.
*
* If the {@link UpdateConstraint.Operator#IS IS} operator is specified
* the specified value will be compared to the topic value for strict
* binary equality. The value type can be any value type supported by
* {@link DataTypes} ({@link Integer} is allowed and is treated as a
* {@link Long}) or any {@link Bytes} value and can be used to compare
* against any topic type.
*
* When a {@link TopicType#STRING STRING}, {@link TopicType#INT64 INT64}
* or {@link TopicType#DOUBLE DOUBLE} topic is updated to a {@code null}
* value, the topic is set to have no value. Use the {@link #noValue()}
* constraint to check if the topic has no value.
*
* This constraint is unsatisfied if no topic is present at the path.
*
* @param operator the operator that determines the type of comparison
*
* @param value the value
*
* @return the constraint
*
* @throws IllegalArgumentException if the the operator is not
* compatible with the value type or the value type is not
* supported
*
* @since 6.10
*/
UpdateConstraint value(
UpdateConstraint.Operator operator,
Object value);
/**
* Create a constraint requiring the topic to have no value.
*
* This constraint is unsatisfied if no topic is present at the path.
*
* @return the constraint
*/
UpdateConstraint noValue();
/**
* Create a constraint requiring the path to have no topic.
*
* This is useful when setting the first value of a topic being added
* using {@link TopicUpdate#addAndSet addAndSet} without changing the
* value if the topic already exists. This constraint is unsatisfied if
* a topic is present at the path.
*
* @return the constraint
*/
UpdateConstraint noTopic();
/**
* Create a constraint that partially matches the current topic value.
*
* The topic must be a {@link TopicType#JSON JSON} topic (or a
* {@link TopicType#TIME_SERIES TIME_SERIES} topic with a json event
* type). The {@link PartialJSON} partially describes the structure of a
* {@link JSON} value. The
* {@link PartialJSON#with(String, UpdateConstraint.Operator, Object)
* with} or {@link PartialJSON#without without} methods must be used to
* fully qualify the constraint.
*
* The constraint is unsatisfied if no topic is present at the path.
*
* @return the constraint
*/
PartialJSON jsonValue();
}
}