diff --git a/client/src/main/java/io/spine/client/CompositeEntityStateFilter.java b/client/src/main/java/io/spine/client/CompositeEntityStateFilter.java new file mode 100644 index 00000000000..acd723db9b0 --- /dev/null +++ b/client/src/main/java/io/spine/client/CompositeEntityStateFilter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import io.spine.base.EntityState; +import io.spine.client.CompositeFilter.CompositeOperator; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.asList; +import static io.spine.client.CompositeFilter.CompositeOperator.ALL; +import static io.spine.client.CompositeFilter.CompositeOperator.EITHER; + +/** + * A subscription filter which aggregates one or more {@link EntityState} filters. + */ +public final class CompositeEntityStateFilter extends TypedCompositeFilter { + + private static final long serialVersionUID = 0L; + + private CompositeEntityStateFilter(Collection filters, + CompositeOperator operator) { + super(filters, operator); + } + + /** + * Creates a new conjunction composite filter. + * + *

A record is considered matching this filter if and only if it matches all of the + * passed filters. + */ + public static CompositeEntityStateFilter + all(EntityStateFilter first, EntityStateFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeEntityStateFilter(asList(first, rest), ALL); + } + + /** + * Creates a new disjunction composite filter. + * + *

A record is considered matching this filter if it matches at least one of the passed + * filters. + */ + public static CompositeEntityStateFilter + either(EntityStateFilter first, EntityStateFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeEntityStateFilter(asList(first, rest), EITHER); + } + +} diff --git a/client/src/main/java/io/spine/client/CompositeEventFilter.java b/client/src/main/java/io/spine/client/CompositeEventFilter.java index 7bf10ec1aac..c89a96eeaaf 100644 --- a/client/src/main/java/io/spine/client/CompositeEventFilter.java +++ b/client/src/main/java/io/spine/client/CompositeEventFilter.java @@ -20,57 +20,53 @@ package io.spine.client; -import com.google.common.collect.ImmutableList; +import io.spine.client.CompositeFilter.CompositeOperator; import io.spine.core.Event; -import java.util.List; +import java.util.Collection; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Lists.asList; +import static io.spine.client.CompositeFilter.CompositeOperator.ALL; +import static io.spine.client.CompositeFilter.CompositeOperator.EITHER; /** - * Filters events by composite criteria which can test both event messages and their contexts. + * A composite subscription filter which can aggregate both event message and event context + * filters. */ -final class CompositeEventFilter implements CompositeMessageFilter { +public final class CompositeEventFilter extends TypedCompositeFilter { - /** The filter data as composed when creating a topic. */ - private final CompositeFilter filter; - /** Filters adopted to filter events basing on the passed filter data. */ - private final ImmutableList> filters; + private static final long serialVersionUID = 0L; - CompositeEventFilter(CompositeFilter filter) { - this.filter = checkNotNull(filter); - this.filters = - filter.getFilterList() - .stream() - .map(EventFilter::new) - .collect(toImmutableList()); - } - - @Override - public List> filters() { - return filters; + private CompositeEventFilter(Collection filters, CompositeOperator operator) { + super(filters, operator); } - @Override - public CompositeFilter.CompositeOperator operator() { - return filter.operator(); + CompositeEventFilter(CompositeFilter filter) { + super(checkNotNull(filter), EventFilter::new); } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CompositeEventFilter other = (CompositeEventFilter) o; - return filter.equals(other.filter); + /** + * Creates a new conjunction composite filter. + * + *

A record is considered matching this filter if and only if it matches all of the passed + * filters. + */ + public static CompositeEventFilter all(EventFilter first, EventFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeEventFilter(asList(first, rest), ALL); } - @Override - public int hashCode() { - return filter.hashCode(); + /** + * Creates a new disjunction composite filter. + * + *

A record is considered matching this filter if it matches at least one of the passed + * filters. + */ + public static CompositeEventFilter either(EventFilter first, EventFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeEventFilter(asList(first, rest), EITHER); } } diff --git a/client/src/main/java/io/spine/client/CompositeQueryFilter.java b/client/src/main/java/io/spine/client/CompositeQueryFilter.java new file mode 100644 index 00000000000..801ad31582e --- /dev/null +++ b/client/src/main/java/io/spine/client/CompositeQueryFilter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import io.spine.base.EntityState; +import io.spine.client.CompositeFilter.CompositeOperator; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.asList; +import static io.spine.client.CompositeFilter.CompositeOperator.ALL; +import static io.spine.client.CompositeFilter.CompositeOperator.EITHER; + +/** + * A composite query filter which targets one or more entity + * {@link io.spine.base.EntityColumn columns}. + */ +public final class CompositeQueryFilter extends TypedCompositeFilter { + + private static final long serialVersionUID = 0L; + + private CompositeQueryFilter(Collection filters, CompositeOperator operator) { + super(filters, operator); + } + + /** + * Creates a new conjunction composite filter. + * + *

A record is considered matching this filter if and only if it matches all of the + * passed filters. + */ + public static CompositeQueryFilter all(QueryFilter first, QueryFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeQueryFilter(asList(first, rest), ALL); + } + + /** + * Creates a new disjunction composite filter. + * + *

A record is considered matching this filter if it matches at least one of the passed + * filters. + */ + public static CompositeQueryFilter either(QueryFilter first, QueryFilter... rest) { + checkNotNull(first); + checkNotNull(rest); + return new CompositeQueryFilter(asList(first, rest), EITHER); + } +} diff --git a/client/src/main/java/io/spine/client/EntityStateFilter.java b/client/src/main/java/io/spine/client/EntityStateFilter.java new file mode 100644 index 00000000000..d42013328e0 --- /dev/null +++ b/client/src/main/java/io/spine/client/EntityStateFilter.java @@ -0,0 +1,132 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import io.spine.base.EntityState; +import io.spine.base.EntityStateField; +import io.spine.client.Filter.Operator; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.client.Filters.checkSupportedOrderingComparisonType; +import static io.spine.client.Filters.createFilter; + +/** + * A subscription filter which targets an {@link EntityState}. + */ +public final class EntityStateFilter extends TypedFilter { + + private static final long serialVersionUID = 0L; + + private EntityStateFilter(EntityStateField field, Object expected, Operator operator) { + super(createFilter(field.getField(), expected, operator)); + } + + /** + * Creates a new equality filter. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EntityStateFilter eq(EntityStateField field, Object value) { + checkNotNull(field); + checkNotNull(value); + return new EntityStateFilter(field, value, EQUAL); + } + + /** + * Creates a new "greater than" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EntityStateFilter gt(EntityStateField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EntityStateFilter(field, value, GREATER_THAN); + } + + /** + * Creates a new "less than" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EntityStateFilter lt(EntityStateField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EntityStateFilter(field, value, LESS_THAN); + } + + /** + * Creates a new "greater than or equals" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EntityStateFilter ge(EntityStateField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EntityStateFilter(field, value, GREATER_OR_EQUAL); + } + + /** + * Creates a new "less than or equals" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EntityStateFilter le(EntityStateField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EntityStateFilter(field, value, LESS_OR_EQUAL); + } +} diff --git a/client/src/main/java/io/spine/client/EventContextField.java b/client/src/main/java/io/spine/client/EventContextField.java deleted file mode 100644 index b1b5d92d047..00000000000 --- a/client/src/main/java/io/spine/client/EventContextField.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.client; - -import io.spine.base.Field; -import io.spine.core.Event; -import io.spine.core.EventContext; - -import static java.lang.String.format; - -/** - * Utility class for obtaining the {@code context} field in the {@code Event} proto type. - * - * @implNote This class uses field numbers instead of string constants, which is safer than - * using string constants for referencing proto fields. In the (unlikely) event of renaming - * the referenced fields, this class would still provide correct references to fields and - * their new names. - */ -final class EventContextField { - - /** References the {@code context} field in {@code Event}. */ - private static final Field CONTEXT_FIELD = - Field.withNumberIn(Event.CONTEXT_FIELD_NUMBER, Event.getDescriptor()); - - /** The name of the {@code context} field. */ - private static final String CONTEXT_FIELD_NAME = CONTEXT_FIELD.toString(); - - private static final String PAST_MESSAGE = pastMessageField(); - - /** Prevents instantiation of this utility class. */ - private EventContextField() { - } - - /** Obtains the instance of the {@code context} field. */ - static Field instance() { - return CONTEXT_FIELD; - } - - /** Obtains the name of the {@code context} field. */ - static String name() { - return CONTEXT_FIELD_NAME; - } - - /** - * Obtains the path to the "context.past_message" field of the {@code Event} proto type. - */ - static String pastMessage() { - return PAST_MESSAGE; - } - - /** - * Obtains the path to the "context.past_message" field of {@code Event} proto type. - * - *

This method is safer than using a string constant because it relies on field numbers, - * rather than names (that might be changed). - */ - private static String pastMessageField() { - Field pastMessage = Field.withNumberIn(EventContext.PAST_MESSAGE_FIELD_NUMBER, - EventContext.getDescriptor()); - return format("%s.%s", name(), pastMessage.toString()); - } -} diff --git a/client/src/main/java/io/spine/client/EventFilter.java b/client/src/main/java/io/spine/client/EventFilter.java index 80665bd15ee..716d565001a 100644 --- a/client/src/main/java/io/spine/client/EventFilter.java +++ b/client/src/main/java/io/spine/client/EventFilter.java @@ -20,49 +20,244 @@ package io.spine.client; +import io.spine.base.EventMessageField; import io.spine.core.Event; +import io.spine.core.EventContextField; import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.client.Filters.checkSupportedOrderingComparisonType; +import static io.spine.client.Filters.createContextFilter; +import static io.spine.client.Filters.createFilter; /** - * Filters events by conditions on both message and context. + * A subscription filter which targets an {@link Event}. + * + *

Can filter events by conditions on both message and context. See factory methods of the + * class for details. */ -final class EventFilter implements MessageFilter { +public final class EventFilter extends TypedFilter { + + private static final long serialVersionUID = 0L; - private final Filter filter; + private final boolean byContext; + private EventFilter(Filter filter, boolean byContext) { + super(filter); + this.byContext = byContext; + } + + private EventFilter(EventMessageField field, Object expected, Filter.Operator operator) { + this(createFilter(field.getField(), expected, operator), false); + } + + private EventFilter(EventContextField field, Object expected, Filter.Operator operator) { + this(createContextFilter(field.getField(), expected, operator), true); + } + + /** + * Creates an instance from the passed {@code Filter} message. + * + *

The filter is considered targeting event context if the field path in the filter starts + * with {@code "context."}. + */ EventFilter(Filter filter) { - this.filter = checkNotNull(filter); + this(filter, isContextFilter(filter)); + } + + /** + * Creates a new equality filter which targets a field in the event message. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter eq(EventMessageField field, Object value) { + checkNotNull(field); + checkNotNull(value); + return new EventFilter(field, value, EQUAL); + } + + /** + * Creates a new equality filter which targets a field in the event context. + * + * @param field + * the context field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter eq(EventContextField field, Object value) { + checkNotNull(field); + checkNotNull(value); + return new EventFilter(field, value, EQUAL); + } + + /** + * Creates a new "greater than" filter which targets a field in the event message. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter gt(EventMessageField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, GREATER_THAN); + } + + /** + * Creates a new "greater than" filter which targets a field in the event context. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the context field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter gt(EventContextField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, GREATER_THAN); + } + + /** + * Creates a new "less than" filter which targets a field in the event message. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter lt(EventMessageField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, LESS_THAN); + } + + /** + * Creates a new "less than" filter which targets a field in the event context. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the context field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter lt(EventContextField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, LESS_THAN); + } + + /** + * Creates a new "greater than or equals" filter which targets a field in the event message. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter ge(EventMessageField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, GREATER_OR_EQUAL); + } + + /** + * Creates a new "greater than or equals" filter which targets a field in the event context. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the context field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter ge(EventContextField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, GREATER_OR_EQUAL); + } + + /** + * Creates a new "less than or equals" filter which targets a field in the event message. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the message field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter le(EventMessageField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, LESS_OR_EQUAL); + } + + /** + * Creates a new "less than or equals" filter which targets a field in the event context. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param field + * the context field from which the actual value is taken + * @param value + * the expected value + */ + public static EventFilter le(EventContextField field, Object value) { + checkNotNull(field); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new EventFilter(field, value, LESS_OR_EQUAL); } @Override public boolean test(Event event) { - boolean byContext = - filter.getFieldPath() - .getFieldName(0) - .equals(EventContextField.name()); if (byContext) { // Since we reference the context field with `context` prefix, we need to pass // the whole `Event` instance. - return filter.test(event); - } - return filter.test(event.enclosedMessage()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + return filter().test(event); } - if (o == null || getClass() != o.getClass()) { - return false; - } - EventFilter other = (EventFilter) o; - return filter.equals(other.filter); + return filter().test(event.enclosedMessage()); } - @Override - public int hashCode() { - return filter.hashCode(); + private static boolean isContextFilter(Filter filter) { + String contextFieldName = Event.Field.context() + .getField() + .toString(); + String firstInPath = filter.getFieldPath() + .getFieldName(0); + boolean result = contextFieldName.equals(firstInPath); + return result; } } diff --git a/client/src/main/java/io/spine/client/EventSubscriptionRequest.java b/client/src/main/java/io/spine/client/EventSubscriptionRequest.java index e86e19049b3..d5f16b8a3cc 100644 --- a/client/src/main/java/io/spine/client/EventSubscriptionRequest.java +++ b/client/src/main/java/io/spine/client/EventSubscriptionRequest.java @@ -28,6 +28,8 @@ import java.util.function.Consumer; import java.util.function.Function; +import static io.spine.client.Filters.extractFilters; + /** * Allows to subscribe to events using filtering conditions. * @@ -35,18 +37,17 @@ * values of the proto types of subscribed messages: *

{@code
  * clientRequest.subscribeToEvent(MyEventMessage.class)
- *              .where(eq("my_proto_field"), fieldValue)
+ *              .where(eq(MyEventMessage.Field.myProtoField(), fieldValue))
  *              .observe((event, context) -> {...})
  *              .post();
  * }
* *

In addition to regular filtering conditions, event subscription requests may also reference - * fields of {@code "spine.core.EventContext"} using {@code "context."} notation. For example, - * in order to filter events originate from commands of the given user, please use the following - * code: + * fields of {@code spine.core.EventContext}. For example, in order to filter events originate from + * commands of the given user, please use the following code: *

{@code
  * clientRequest.subscribeToEvent(MyEventMessage.class)
- *              .where(eq("context.past_message.actor_context.actor"), userId)
+ *              .where(eq(EventContext.Field.pastMessage().actorContext().actor(), userId))
  *              .observe((event, context) -> {...})
  *              .post();
  * }
@@ -64,6 +65,28 @@ public final class EventSubscriptionRequest this.consumers = EventConsumers.newBuilder(); } + /** + * Configures the request to return results matching all the passed filters. + * + *

Please note that the {@link EventFilter} instances may target both event message and + * event context fields. See {@link EventFilter} for details. + */ + public EventSubscriptionRequest where(EventFilter... filter) { + builder().where(extractFilters(filter)); + return self(); + } + + /** + * Configures the request to return results matching all the passed filters. + * + *

Please note that the {@link CompositeEventFilter} instances may target both event message + * and event context fields. See {@link CompositeEventFilter} for details. + */ + public EventSubscriptionRequest where(CompositeEventFilter... filter) { + builder().where(extractFilters(filter)); + return self(); + } + @Override EventConsumers.Builder consumers() { return consumers; diff --git a/client/src/main/java/io/spine/client/EventsAfterCommand.java b/client/src/main/java/io/spine/client/EventsAfterCommand.java index 15dd30d1342..c3bd68a07ae 100644 --- a/client/src/main/java/io/spine/client/EventsAfterCommand.java +++ b/client/src/main/java/io/spine/client/EventsAfterCommand.java @@ -23,8 +23,10 @@ import com.google.common.collect.ImmutableSet; import io.grpc.stub.StreamObserver; import io.spine.base.EventMessage; +import io.spine.base.Field; import io.spine.core.Command; import io.spine.core.Event; +import io.spine.core.EventContext; import io.spine.core.UserId; import io.spine.logging.Logging; import org.checkerframework.checker.nullness.qual.Nullable; @@ -75,11 +77,16 @@ private ImmutableSet subscribeWith(@Nullable ErrorHandler errorHan } /** - * Creates subscription topics for the subscribed events which has the passed command + * Creates subscription topics for the subscribed events which have the passed command * as the origin. */ private ImmutableSet eventsOf(Command c) { - String fieldName = EventContextField.pastMessage(); + Field pastMessage = EventContext.Field.pastMessage() + .getField(); + String fieldName = Event.Field.context() + .getField() + .nested(pastMessage) + .toString(); ImmutableSet> eventTypes = consumers.eventTypes(); TopicFactory topic = client.requestOf(user) .topic(); diff --git a/client/src/main/java/io/spine/client/FilteringField.java b/client/src/main/java/io/spine/client/FilteringField.java index bd112cd309c..bb5e78afe18 100644 --- a/client/src/main/java/io/spine/client/FilteringField.java +++ b/client/src/main/java/io/spine/client/FilteringField.java @@ -27,6 +27,7 @@ import io.spine.base.Field; import io.spine.base.FieldPath; import io.spine.code.proto.FieldDeclaration; +import io.spine.core.Event; import io.spine.core.EventContext; import java.util.Optional; @@ -77,9 +78,12 @@ private void checkFieldOfEvent(Descriptor descriptor) { } private boolean refersToContext() { - String firstInPath = field.path() - .getFieldName(0); - return firstInPath.equals(EventContextField.name()); + String contextFieldName = Event.Field.context() + .getField() + .toString(); + String firstInPath = field.path().getFieldName(0); + boolean result = contextFieldName.equals(firstInPath); + return result; } private void checkPresentInEventContext() { diff --git a/client/src/main/java/io/spine/client/FilteringRequest.java b/client/src/main/java/io/spine/client/FilteringRequest.java index b4bf4d85d3b..ee8ae1d58dd 100644 --- a/client/src/main/java/io/spine/client/FilteringRequest.java +++ b/client/src/main/java/io/spine/client/FilteringRequest.java @@ -139,7 +139,11 @@ public B byId(String... ids) { /** * Configures the request to return results matching all the passed filters. + * + * @deprecated Please use the overloads from the descendants that rely on strongly-typed + * filters. */ + @Deprecated public B where(Filter... filter) { builder().where(filter); return self(); @@ -147,7 +151,11 @@ public B where(Filter... filter) { /** * Configures the request to return results matching all the passed filters. + * + * @deprecated Please use the overloads from the descendants that rely on strongly-typed + * filters. */ + @Deprecated public B where(CompositeFilter... filter) { builder().where(filter); return self(); diff --git a/client/src/main/java/io/spine/client/Filters.java b/client/src/main/java/io/spine/client/Filters.java index 498dcd1e2a8..62bc8fa518a 100644 --- a/client/src/main/java/io/spine/client/Filters.java +++ b/client/src/main/java/io/spine/client/Filters.java @@ -20,21 +20,25 @@ package io.spine.client; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Primitives; import com.google.protobuf.Any; +import com.google.protobuf.Message; import com.google.protobuf.Timestamp; import io.spine.annotation.Internal; import io.spine.base.Field; import io.spine.base.FieldPath; import io.spine.client.CompositeFilter.CompositeOperator; -import io.spine.core.Version; +import io.spine.code.proto.FieldName; import io.spine.core.Event; +import io.spine.core.Version; import java.util.Collection; import java.util.function.Predicate; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Lists.asList; import static io.spine.client.CompositeFilter.CompositeOperator.ALL; import static io.spine.client.CompositeFilter.CompositeOperator.EITHER; @@ -45,6 +49,7 @@ import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; import static io.spine.client.Filter.Operator.LESS_THAN; import static io.spine.protobuf.TypeConverter.toAny; +import static java.util.Arrays.stream; /** * A factory of {@link Filter} instances. @@ -73,6 +78,9 @@ * * * @see QueryBuilder for the application + * @see QueryFilter + * @see EntityStateFilter + * @see EventFilter */ public final class Filters { @@ -81,7 +89,7 @@ private Filters() { } /** - * Creates new equality {@link Filter}. + * Creates a new equality {@link Filter}. * * @param fieldPath * the field path or the entity column name for entity filters @@ -96,7 +104,7 @@ public static Filter eq(String fieldPath, Object value) { } /** - * Creates new "greater than" {@link Filter}. + * Creates a new "greater than" {@link Filter}. * *

For the supported types description see Comparison types section. * @@ -114,7 +122,7 @@ public static Filter gt(String fieldPath, Object value) { } /** - * Creates new "less than" {@link Filter}. + * Creates a new "less than" {@link Filter}. * *

See Comparison types section for the supported types description. * @@ -132,7 +140,7 @@ public static Filter lt(String fieldPath, Object value) { } /** - * Creates new "greater or equal" {@link Filter}. + * Creates a new "greater or equal" {@link Filter}. * *

See Comparison types section for the supported types description. * @@ -150,7 +158,7 @@ public static Filter ge(String fieldPath, Object value) { } /** - * Creates new "less or equal" {@link Filter}. + * Creates a new "less or equal" {@link Filter}. * *

See Comparison types section for the supported types description. * @@ -168,7 +176,7 @@ public static Filter le(String fieldPath, Object value) { } /** - * Creates new conjunction composite filter. + * Creates a new conjunction composite filter. * *

A record is considered matching this filter if and only if it matches all of the * aggregated filters. @@ -188,7 +196,7 @@ public static CompositeFilter all(Filter first, Filter... rest) { } /** - * Creates new disjunction composite filter. + * Creates a new disjunction composite filter. * *

A record is considered matching this filter if it matches at least one of the aggregated * filters. @@ -206,7 +214,7 @@ public static CompositeFilter either(Filter first, Filter... rest) { } /** - * Creates new conjunction composite filter. + * Creates a new conjunction composite filter. * *

A record is considered matching this filter if and only if it matches all of * the aggregated filters. @@ -226,8 +234,22 @@ static CompositeFilter all(Collection filters) { return composeFilters(filters, ALL); } - private static Filter createFilter(String fieldPath, Object value, Operator operator) { - FieldPath path = Field.parse(fieldPath).path(); + static Filter createFilter(String fieldPath, Object value, Operator operator) { + Field field = Field.parse(fieldPath); + return createFilter(field, value, operator); + } + + static Filter createFilter(FieldName fieldName, Object value, Operator operator) { + FieldPath fieldPath = fieldName.asPath(); + return createFilter(fieldPath, value, operator); + } + + static Filter createFilter(Field field, Object value, Operator operator) { + FieldPath fieldPath = field.path(); + return createFilter(fieldPath, value, operator); + } + + static Filter createFilter(FieldPath path, Object value, Operator operator) { Any wrappedValue = toAny(value); Filter filter = Filter .newBuilder() @@ -238,8 +260,15 @@ private static Filter createFilter(String fieldPath, Object value, Operator oper return filter; } - private static CompositeFilter composeFilters(Collection filters, - CompositeOperator operator) { + static Filter createContextFilter(Field field, Object value, Operator operator) { + FieldPath fieldPath = Event.Field.context() + .getField() + .nested(field) + .path(); + return createFilter(fieldPath, value, operator); + } + + static CompositeFilter composeFilters(Iterable filters, CompositeOperator operator) { CompositeFilter result = CompositeFilter .newBuilder() .addAllFilter(filters) @@ -248,7 +277,26 @@ private static CompositeFilter composeFilters(Collection filters, return result; } - private static void checkSupportedOrderingComparisonType(Class cls) { + static Filter[] extractFilters(TypedFilter[] filters) { + return stream(filters) + .map(TypedFilter::filter) + .toArray(Filter[]::new); + } + + static ImmutableList + extractFilters(Collection> filters) { + return filters.stream() + .map(TypedFilter::filter) + .collect(toImmutableList()); + } + + static CompositeFilter[] extractFilters(TypedCompositeFilter[] filters) { + return stream(filters) + .map(TypedCompositeFilter::filter) + .toArray(CompositeFilter[]::new); + } + + static void checkSupportedOrderingComparisonType(Class cls) { Class dataType = Primitives.wrap(cls); boolean supported = isSupportedNumber(dataType) || Timestamp.class.isAssignableFrom(dataType) @@ -269,7 +317,8 @@ private static boolean isSupportedNumber(Class wrapperClass) { * Creates a filter of events which can apply conditions from the passed * {@code CompositeFilter} to both event message and its context. * - *

Please use the {@code "context."} prefix for referencing a field of the event context. + *

The filter is deemed addressing the event context if the field path specified in it + * starts with the {@code "context."} prefix. */ @Internal public static Predicate toEventFilter(CompositeFilter filterData) { diff --git a/client/src/main/java/io/spine/client/QueryFilter.java b/client/src/main/java/io/spine/client/QueryFilter.java new file mode 100644 index 00000000000..d18be6b605e --- /dev/null +++ b/client/src/main/java/io/spine/client/QueryFilter.java @@ -0,0 +1,132 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import io.spine.base.EntityColumn; +import io.spine.base.EntityState; +import io.spine.client.Filter.Operator; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.client.Filters.checkSupportedOrderingComparisonType; +import static io.spine.client.Filters.createFilter; + +/** + * A query filter which targets a {@linkplain EntityColumn column} of an entity. + */ +public final class QueryFilter extends TypedFilter { + + private static final long serialVersionUID = 0L; + + private QueryFilter(EntityColumn column, Object expected, Operator operator) { + super(createFilter(column.name(), expected, operator)); + } + + /** + * Creates a new equality filter. + * + * @param column + * the entity column from which the actual value is taken + * @param value + * the expected value + */ + public static QueryFilter eq(EntityColumn column, Object value) { + checkNotNull(column); + checkNotNull(value); + return new QueryFilter(column, value, EQUAL); + } + + /** + * Creates a new "greater than" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param column + * the entity column from which the actual value is taken + * @param value + * the expected value + */ + public static QueryFilter gt(EntityColumn column, Object value) { + checkNotNull(column); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new QueryFilter(column, value, GREATER_THAN); + } + + /** + * Creates a new "less than" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param column + * the entity column from which the actual value is taken + * @param value + * the expected value + */ + public static QueryFilter lt(EntityColumn column, Object value) { + checkNotNull(column); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new QueryFilter(column, value, LESS_THAN); + } + + /** + * Creates a new "greater than or equals" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param column + * the entity column from which the actual value is taken + * @param value + * the expected value + */ + public static QueryFilter ge(EntityColumn column, Object value) { + checkNotNull(column); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new QueryFilter(column, value, GREATER_OR_EQUAL); + } + + /** + * Creates a new "less than or equals" filter. + * + *

NOTE: not all value types are supported for ordering comparison. See {@link Filters} for + * details. + * + * @param column + * the entity column from which the actual value is taken + * @param value + * the expected value + */ + public static QueryFilter le(EntityColumn column, Object value) { + checkNotNull(column); + checkNotNull(value); + checkSupportedOrderingComparisonType(value.getClass()); + return new QueryFilter(column, value, LESS_OR_EQUAL); + } +} diff --git a/client/src/main/java/io/spine/client/QueryRequest.java b/client/src/main/java/io/spine/client/QueryRequest.java index cb41c90a8d6..761c51938db 100644 --- a/client/src/main/java/io/spine/client/QueryRequest.java +++ b/client/src/main/java/io/spine/client/QueryRequest.java @@ -21,11 +21,13 @@ package io.spine.client; import com.google.common.collect.ImmutableList; +import io.spine.base.EntityColumn; import io.spine.base.EntityState; import java.util.function.Function; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.spine.client.Filters.extractFilters; /** * Allows to create a post a query for messages of the given type. @@ -39,16 +41,16 @@ * .select(Customer.class) * .byId(westCoastCustomerIds()) * .withMask("name", "address", "email") - * .where(eq("type", "permanent"), - * eq("discountPercent", 10), - * eq("companySize", Company.Size.SMALL)) - * .orderBy("name", ASCENDING) + * .where(eq(Customer.Column.type(), "permanent"), + * eq(Customer.Column.discountPercent(), 10), + * eq(Customer.Column.companySize(), Company.Size.SMALL)) + * .orderBy(Customer.Column.name(), ASCENDING) * .limit(20) * .run(); * } * - *

Filtering by field values (via {@link #where(Filter...)} and - * {@link #where(CompositeFilter...)} methods) can be composed using the {@link Filters} + *

Filtering by field values (via {@link #where(QueryFilter...)} and + * {@link #where(CompositeQueryFilter...)} methods) can be composed using the {@link Filters} * utility class. * * @param @@ -62,6 +64,22 @@ public final class QueryRequest super(parent, type); } + /** + * Configures the request to return results matching all the passed filters. + */ + public QueryRequest where(QueryFilter... filter) { + builder().where(extractFilters(filter)); + return this; + } + + /** + * Configures the request to return results matching all the passed filters. + */ + public QueryRequest where(CompositeQueryFilter... filter) { + builder().where(extractFilters(filter)); + return this; + } + /** * Sets the sorting order by the target column and order direction. * @@ -69,12 +87,30 @@ public final class QueryRequest * the column to sort by * @param direction * sorting direction + * @deprecated Please use the {@linkplain #orderBy(EntityColumn, OrderBy.Direction) alternative} + * which relies on strongly-typed columns instead. */ + @Deprecated public QueryRequest orderBy(String column, OrderBy.Direction direction) { builder().orderBy(column, direction); return this; } + /** + * Sets the sorting order by the target column and order direction. + * + * @param column + * the column to sort by + * @param direction + * sorting direction + */ + public QueryRequest orderBy(EntityColumn column, OrderBy.Direction direction) { + String columnName = column.name() + .value(); + builder().orderBy(columnName, direction); + return this; + } + /** * Limits the number of results returned by the query. * diff --git a/client/src/main/java/io/spine/client/SubscriptionRequest.java b/client/src/main/java/io/spine/client/SubscriptionRequest.java index 9a3cd0fdae9..1b4acd0610b 100644 --- a/client/src/main/java/io/spine/client/SubscriptionRequest.java +++ b/client/src/main/java/io/spine/client/SubscriptionRequest.java @@ -26,6 +26,8 @@ import java.util.function.Consumer; import java.util.function.Function; +import static io.spine.client.Filters.extractFilters; + /** * Allows to subscribe to updates of entity states using filtering conditions. * @@ -42,6 +44,22 @@ public final class SubscriptionRequest this.consumers = StateConsumers.newBuilder(); } + /** + * Configures the request to return results matching all the passed filters. + */ + public SubscriptionRequest where(EntityStateFilter... filter) { + builder().where(extractFilters(filter)); + return self(); + } + + /** + * Configures the request to return results matching all the passed filters. + */ + public SubscriptionRequest where(CompositeEntityStateFilter... filter) { + builder().where(extractFilters(filter)); + return self(); + } + @Override StateConsumers.Builder consumers() { return consumers; diff --git a/client/src/main/java/io/spine/client/TypedCompositeFilter.java b/client/src/main/java/io/spine/client/TypedCompositeFilter.java new file mode 100644 index 00000000000..5cf52428cfa --- /dev/null +++ b/client/src/main/java/io/spine/client/TypedCompositeFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Message; +import io.spine.client.CompositeFilter.CompositeOperator; +import io.spine.value.ValueHolder; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.spine.client.Filters.composeFilters; +import static io.spine.client.Filters.extractFilters; + +/** + * A typed wrapper around the {@link CompositeFilter} instance. + * + * @param + * the type of a filtered message + */ +abstract class TypedCompositeFilter + extends ValueHolder + implements CompositeMessageFilter { + + private static final long serialVersionUID = 0L; + + private final ImmutableList> filters; + + TypedCompositeFilter(CompositeFilter filter, Function> wrapper) { + super(filter); + this.filters = filter.getFilterList() + .stream() + .map(wrapper) + .collect(toImmutableList()); + } + + TypedCompositeFilter(Collection> filters, + CompositeOperator operator) { + super(composeFilters(extractFilters(filters), operator)); + this.filters = ImmutableList.copyOf(filters); + } + + CompositeFilter filter() { + return value(); + } + + @Override + public List> filters() { + return filters; + } + + @Override + public CompositeOperator operator() { + return filter().operator(); + } +} diff --git a/client/src/main/java/io/spine/client/TypedFilter.java b/client/src/main/java/io/spine/client/TypedFilter.java new file mode 100644 index 00000000000..8366cfed912 --- /dev/null +++ b/client/src/main/java/io/spine/client/TypedFilter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import com.google.protobuf.Message; +import io.spine.value.ValueHolder; + +/** + * A typed wrapper around the {@link Filter} instance. + * + * @param + * the type of a filtered message + */ +@SuppressWarnings("AbstractClassWithoutAbstractMethods") +// Prevent instantiation of this abstract filter in favor of concrete descendants. +abstract class TypedFilter + extends ValueHolder + implements MessageFilter { + + private static final long serialVersionUID = 0L; + + TypedFilter(Filter value) { + super(value); + } + + Filter filter() { + return value(); + } + + @Override + public boolean test(M m) { + return filter().test(m); + } +} diff --git a/client/src/main/proto/spine/client/filters.proto b/client/src/main/proto/spine/client/filters.proto index 1a9ad6cd397..6ab8796b8a3 100644 --- a/client/src/main/proto/spine/client/filters.proto +++ b/client/src/main/proto/spine/client/filters.proto @@ -104,7 +104,10 @@ message Filter { // The path to the field to be matched. // - // For entities, the path contains the only entry being the name of entity column. + // If the filter is a query filter, this path should contain a top-level message field that + // is marked with the `(column)` option. For subscriptions, any message fields can be specified + // including nested ones. + // base.FieldPath field_path = 1 [(required) = true]; // The value to compare upon. diff --git a/client/src/test/java/io/spine/client/EntityStateFilterTest.java b/client/src/test/java/io/spine/client/EntityStateFilterTest.java new file mode 100644 index 00000000000..4666ebc84a0 --- /dev/null +++ b/client/src/test/java/io/spine/client/EntityStateFilterTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import com.google.common.testing.NullPointerTester; +import com.google.protobuf.Int32Value; +import io.spine.base.EntityStateField; +import io.spine.base.FieldPath; +import io.spine.protobuf.AnyPacker; +import io.spine.test.client.TestEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.function.BiFunction; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; + +@DisplayName("`EntityStateFilter` should") +class EntityStateFilterTest { + + @Test + @DisplayName(NOT_ACCEPT_NULLS) + void passNullToleranceCheck() { + new NullPointerTester() + .setDefault(EntityStateField.class, TestEntity.Field.firstField()) + .testAllPublicStaticMethods(EntityStateFilter.class); + } + + @Nested + @DisplayName("create") + class Create { + + @Test + @DisplayName("an `equals` filter") + void eqFilter() { + checkCreates(EntityStateFilter::eq, EQUAL); + } + + @Test + @DisplayName("a `greater than` filter") + void gtFilter() { + checkCreates(EntityStateFilter::gt, GREATER_THAN); + } + + @Test + @DisplayName("a `less than` filter") + void ltFilter() { + checkCreates(EntityStateFilter::lt, LESS_THAN); + } + + @Test + @DisplayName("a `greater than or equals` filter") + void geFilter() { + checkCreates(EntityStateFilter::ge, GREATER_OR_EQUAL); + } + + @Test + @DisplayName("a `less than or equals` filter") + void leFilter() { + checkCreates(EntityStateFilter::le, LESS_OR_EQUAL); + } + + private void + checkCreates(BiFunction factoryMethod, + Filter.Operator expectedOperator) { + EntityStateField field = TestEntity.Field.thirdField(); + int value = 42; + EntityStateFilter stateFilter = factoryMethod.apply(field, value); + Filter filter = stateFilter.filter(); + + FieldPath fieldPath = filter.getFieldPath(); + int nameCount = fieldPath.getFieldNameCount(); + assertThat(nameCount).isEqualTo(1); + + String fieldName = fieldPath.getFieldName(0); + String expectedFieldName = field.getField() + .toString(); + assertThat(fieldName).isEqualTo(expectedFieldName); + + assertThat(filter.getOperator()).isEqualTo(expectedOperator); + + Int32Value unpacked = AnyPacker.unpack(filter.getValue(), Int32Value.class); + assertThat(unpacked.getValue()).isEqualTo(value); + } + } +} diff --git a/client/src/test/java/io/spine/client/EventFilterTest.java b/client/src/test/java/io/spine/client/EventFilterTest.java new file mode 100644 index 00000000000..428e4185b95 --- /dev/null +++ b/client/src/test/java/io/spine/client/EventFilterTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import com.google.common.testing.NullPointerTester; +import com.google.protobuf.StringValue; +import io.spine.base.EventMessageField; +import io.spine.base.Field; +import io.spine.base.FieldPath; +import io.spine.core.EventContext; +import io.spine.core.EventContextField; +import io.spine.protobuf.AnyPacker; +import io.spine.test.client.ClProjectCreated; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.function.BiFunction; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; +import static java.lang.String.format; + +@DisplayName("`EventFilter` should") +class EventFilterTest { + + @Test + @DisplayName(NOT_ACCEPT_NULLS) + void passNullToleranceCheck() { + new NullPointerTester() + .setDefault(EventMessageField.class, ClProjectCreated.Field.name()) + .setDefault(EventContextField.class, EventContext.Field.external()) + .testAllPublicStaticMethods(EventFilter.class); + } + + @Nested + @DisplayName("create a filter targeting event message and having operator") + class CreateForEventMessage { + + @Test + @DisplayName("`equals`") + void eqFilter() { + checkCreates(EventFilter::eq, EQUAL); + } + + @Test + @DisplayName("`greater than`") + void gtFilter() { + checkCreates(EventFilter::gt, GREATER_THAN); + } + + @Test + @DisplayName("`less than`") + void ltFilter() { + checkCreates(EventFilter::lt, LESS_THAN); + } + + @Test + @DisplayName("`greater than or equals`") + void geFilter() { + checkCreates(EventFilter::ge, GREATER_OR_EQUAL); + } + + @Test + @DisplayName("`less than or equals`") + void leFilter() { + checkCreates(EventFilter::le, LESS_OR_EQUAL); + } + + private void + checkCreates(BiFunction factoryMethod, + Filter.Operator expectedOperator) { + EventMessageField field = ClProjectCreated.Field.id(); + String value = "some-ID"; + EventFilter eventFilter = factoryMethod.apply(field, value); + Filter filter = eventFilter.filter(); + + FieldPath fieldPath = filter.getFieldPath(); + int nameCount = fieldPath.getFieldNameCount(); + assertThat(nameCount).isEqualTo(1); + + String fieldName = fieldPath.getFieldName(0); + String expectedFieldName = field.getField() + .toString(); + assertThat(fieldName).isEqualTo(expectedFieldName); + + assertThat(filter.getOperator()).isEqualTo(expectedOperator); + + StringValue unpacked = AnyPacker.unpack(filter.getValue(), StringValue.class); + assertThat(unpacked.getValue()).isEqualTo(value); + } + } + + @Nested + @DisplayName("create a filter targeting event context and having operator") + class CreateForEventContext { + + @Test + @DisplayName("`equals`") + void eqFilter() { + checkCreates(EventFilter::eq, EQUAL); + } + + @Test + @DisplayName("`greater than`") + void gtFilter() { + checkCreates(EventFilter::gt, GREATER_THAN); + } + + @Test + @DisplayName("`less than`") + void ltFilter() { + checkCreates(EventFilter::lt, LESS_THAN); + } + + @Test + @DisplayName("`greater than or equals`") + void geFilter() { + checkCreates(EventFilter::ge, GREATER_OR_EQUAL); + } + + @Test + @DisplayName("`less than or equals`") + void leFilter() { + checkCreates(EventFilter::le, LESS_OR_EQUAL); + } + + private void + checkCreates(BiFunction factoryMethod, + Filter.Operator expectedOperator) { + EventContextField field = EventContext.Field.commandId() + .uuid(); + String value = "some-UUID"; + EventFilter eventFilter = factoryMethod.apply(field, value); + Filter filter = eventFilter.filter(); + + FieldPath fieldPath = filter.getFieldPath(); + int nameCount = fieldPath.getFieldNameCount(); + assertThat(nameCount).isEqualTo(3); + + String actualFieldPath = Field.withPath(fieldPath) + .toString(); + String expectedFieldPath = format("context.%s", field.getField()); + assertThat(actualFieldPath).isEqualTo(expectedFieldPath); + + assertThat(filter.getOperator()).isEqualTo(expectedOperator); + + StringValue unpacked = AnyPacker.unpack(filter.getValue(), StringValue.class); + assertThat(unpacked.getValue()).isEqualTo(value); + } + } +} diff --git a/client/src/test/java/io/spine/client/FiltersTest.java b/client/src/test/java/io/spine/client/FiltersTest.java index 6f1d6035078..2b191837519 100644 --- a/client/src/test/java/io/spine/client/FiltersTest.java +++ b/client/src/test/java/io/spine/client/FiltersTest.java @@ -22,12 +22,14 @@ import com.google.common.testing.NullPointerTester; import com.google.protobuf.DoubleValue; -import com.google.protobuf.ProtocolStringList; import com.google.protobuf.StringValue; import com.google.protobuf.Timestamp; +import io.spine.base.Field; +import io.spine.base.FieldPath; import io.spine.client.Filter.Operator; import io.spine.core.Version; import io.spine.core.Versions; +import io.spine.test.client.TestEntityOwner; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -53,20 +55,21 @@ import static io.spine.protobuf.AnyPacker.pack; import static io.spine.protobuf.AnyPacker.unpack; import static io.spine.protobuf.TypeConverter.toAny; +import static io.spine.test.client.TestEntityOwner.Role.ADMIN; import static io.spine.testing.DisplayNames.HAVE_PARAMETERLESS_CTOR; import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; import static io.spine.testing.Tests.assertHasPrivateParameterlessCtor; -import static java.lang.String.join; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -@DisplayName("Filters utility should") +@DisplayName("`Filters` utility should") class FiltersTest { - private static final String FIELD_PATH = "some.field.path"; + private static final String FIELD = "owner.when_last_visited"; private static final Timestamp REQUESTED_VALUE = currentTime(); - private static final String ENUM_FIELD_PATH = "enum.field"; - private static final Operator ENUM_VALUE = EQUAL; + + private static final String ENUM_FIELD = "owner.role"; + private static final TestEntityOwner.Role ENUM_VALUE = ADMIN; @Test @DisplayName(HAVE_PARAMETERLESS_CTOR) @@ -78,7 +81,6 @@ void haveUtilityConstructor() { @DisplayName(NOT_ACCEPT_NULLS) void passNullToleranceCheck() { new NullPointerTester() - .setDefault(Timestamp.class, Timestamp.getDefaultInstance()) .setDefault(Filter.class, Filter.getDefaultInstance()) .testAllPublicStaticMethods(Filters.class); } @@ -90,48 +92,50 @@ class CreateFilterOfType { @Test @DisplayName("`equals`") void equals() { - checkCreatesInstance(eq(FIELD_PATH, REQUESTED_VALUE), EQUAL); + checkCreatesInstance(eq(FIELD, REQUESTED_VALUE), EQUAL); } @Test @DisplayName("`greater than`") void greaterThan() { - checkCreatesInstance(gt(FIELD_PATH, REQUESTED_VALUE), GREATER_THAN); + checkCreatesInstance(gt(FIELD, REQUESTED_VALUE), GREATER_THAN); } @Test @DisplayName("`greater than or equals`") void greaterOrEqual() { - checkCreatesInstance(ge(FIELD_PATH, REQUESTED_VALUE), GREATER_OR_EQUAL); + checkCreatesInstance(ge(FIELD, REQUESTED_VALUE), GREATER_OR_EQUAL); } @Test @DisplayName("`less than`") void lessThan() { - checkCreatesInstance(lt(FIELD_PATH, REQUESTED_VALUE), LESS_THAN); + checkCreatesInstance(lt(FIELD, REQUESTED_VALUE), LESS_THAN); } @Test @DisplayName("`less than or equals`") void lessOrEqual() { - checkCreatesInstance(le(FIELD_PATH, REQUESTED_VALUE), LESS_OR_EQUAL); + checkCreatesInstance(le(FIELD, REQUESTED_VALUE), LESS_OR_EQUAL); } @Test @DisplayName("`equals` for enumerated types") void equalsForEnum() { - Filter filter = eq(ENUM_FIELD_PATH, ENUM_VALUE); - ProtocolStringList pathElements = filter.getFieldPath() - .getFieldNameList(); - assertEquals(ENUM_FIELD_PATH, join(".", pathElements)); + Filter filter = eq(ENUM_FIELD, ENUM_VALUE); + + FieldPath fieldPath = filter.getFieldPath(); + String actual = Field.withPath(fieldPath) + .toString(); + assertEquals(ENUM_FIELD, actual); assertEquals(toAny(ENUM_VALUE), filter.getValue()); assertEquals(EQUAL, filter.getOperator()); } private void checkCreatesInstance(Filter filter, Operator operator) { - ProtocolStringList pathElements = filter.getFieldPath() - .getFieldNameList(); - assertEquals(FIELD_PATH, join(".", pathElements)); + String actual = Field.withPath(filter.getFieldPath()) + .toString(); + assertEquals(FIELD, actual); assertEquals(pack(REQUESTED_VALUE), filter.getValue()); assertEquals(operator, filter.getOperator()); } @@ -145,8 +149,8 @@ class CreateCompositeFilterOfType { @DisplayName("`all`") void all() { Filter[] filters = { - le(FIELD_PATH, REQUESTED_VALUE), - ge(FIELD_PATH, REQUESTED_VALUE) + le(FIELD, REQUESTED_VALUE), + ge(FIELD, REQUESTED_VALUE) }; checkCreatesInstance(Filters.all(filters[0], filters[1]), ALL, filters); } @@ -155,8 +159,8 @@ void all() { @DisplayName("`either`") void either() { Filter[] filters = { - lt(FIELD_PATH, REQUESTED_VALUE), - gt(FIELD_PATH, REQUESTED_VALUE) + lt(FIELD, REQUESTED_VALUE), + gt(FIELD, REQUESTED_VALUE) }; checkCreatesInstance(Filters.either(filters[0], filters[1]), EITHER, filters); } @@ -165,7 +169,8 @@ private void checkCreatesInstance(CompositeFilter filter, CompositeOperator operator, Filter[] groupedFilters) { assertEquals(operator, filter.getOperator()); - assertThat(filter.getFilterList()).containsAtLeastElementsIn(groupedFilters); + assertThat(filter.getFilterList()) + .containsExactlyElementsIn(groupedFilters); } } @@ -177,8 +182,7 @@ class CreateOrderingFilter { @DisplayName("for numbers") void forNumbers() { double number = 3.14; - Filter filter = le("double_field", number); - assertThat(filter).isNotNull(); + Filter filter = le("third_field", number); assertThat(filter.getOperator()).isEqualTo(LESS_OR_EQUAL); DoubleValue value = unpack(filter.getValue(), DoubleValue.class); @@ -189,8 +193,7 @@ void forNumbers() { @DisplayName("for strings") void forStrings() { String theString = "abc"; - Filter filter = gt("string_field", theString); - assertThat(filter).isNotNull(); + Filter filter = gt("first_field", theString); assertThat(filter.getOperator()).isEqualTo(GREATER_THAN); StringValue value = unpack(filter.getValue(), StringValue.class); @@ -201,8 +204,8 @@ void forStrings() { @DisplayName("for timestamps") void forTimestamps() { Timestamp timestamp = currentTime(); - Filter filter = gt("timestamp_field", timestamp); - assertThat(filter).isNotNull(); + Filter filter = gt(FIELD, timestamp); + assertThat(filter.getOperator()).isEqualTo(GREATER_THAN); Timestamp value = unpack(filter.getValue(), Timestamp.class); assertThat(value).isEqualTo(timestamp); @@ -212,7 +215,8 @@ void forTimestamps() { @DisplayName("for versions") void forVersions() { Version version = Versions.zero(); - Filter filter = ge("version_field", version); + Filter filter = ge("some_version_field", version); + assertThat(filter).isNotNull(); assertThat(filter.getOperator()).isEqualTo(GREATER_OR_EQUAL); Version value = unpack(filter.getValue(), Version.class); @@ -228,7 +232,7 @@ class FailToCreateOrderingFilter { @DisplayName("for enumerated types") void forEnums() { assertThrows(IllegalArgumentException.class, - () -> ge(ENUM_FIELD_PATH, ENUM_VALUE)); + () -> ge(ENUM_FIELD, ENUM_VALUE)); } @Test diff --git a/client/src/test/java/io/spine/client/QueryFilterTest.java b/client/src/test/java/io/spine/client/QueryFilterTest.java new file mode 100644 index 00000000000..7b8c35a2e9a --- /dev/null +++ b/client/src/test/java/io/spine/client/QueryFilterTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.client; + +import com.google.common.testing.NullPointerTester; +import com.google.protobuf.Int32Value; +import io.spine.base.EntityColumn; +import io.spine.base.FieldPath; +import io.spine.protobuf.AnyPacker; +import io.spine.test.client.TestEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.function.BiFunction; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.client.Filter.Operator.EQUAL; +import static io.spine.client.Filter.Operator.GREATER_OR_EQUAL; +import static io.spine.client.Filter.Operator.GREATER_THAN; +import static io.spine.client.Filter.Operator.LESS_OR_EQUAL; +import static io.spine.client.Filter.Operator.LESS_THAN; +import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; + +@DisplayName("`QueryFilter` should") +class QueryFilterTest { + + @Test + @DisplayName(NOT_ACCEPT_NULLS) + void passNullToleranceCheck() { + new NullPointerTester() + .setDefault(EntityColumn.class, TestEntity.Column.firstField()) + .testAllPublicStaticMethods(QueryFilter.class); + } + + @Nested + @DisplayName("create") + class Create { + + @Test + @DisplayName("an `equals` filter") + void eqFilter() { + checkCreates(QueryFilter::eq, EQUAL); + } + + @Test + @DisplayName("a `greater than` filter") + void gtFilter() { + checkCreates(QueryFilter::gt, GREATER_THAN); + } + + @Test + @DisplayName("a `less than` filter") + void ltFilter() { + checkCreates(QueryFilter::lt, LESS_THAN); + } + + @Test + @DisplayName("a `greater than or equals` filter") + void geFilter() { + checkCreates(QueryFilter::ge, GREATER_OR_EQUAL); + } + + @Test + @DisplayName("a `less than or equals` filter") + void leFilter() { + checkCreates(QueryFilter::le, LESS_OR_EQUAL); + } + + private void checkCreates(BiFunction factoryMethod, + Filter.Operator expectedOperator) { + EntityColumn column = TestEntity.Column.thirdField(); + int value = 42; + QueryFilter queryFilter = factoryMethod.apply(column, value); + Filter filter = queryFilter.filter(); + + FieldPath fieldPath = filter.getFieldPath(); + int nameCount = fieldPath.getFieldNameCount(); + assertThat(nameCount).isEqualTo(1); + + String fieldName = fieldPath.getFieldName(0); + String columnName = column.name() + .value(); + assertThat(fieldName).isEqualTo(columnName); + + assertThat(filter.getOperator()).isEqualTo(expectedOperator); + + Int32Value unpacked = AnyPacker.unpack(filter.getValue(), Int32Value.class); + assertThat(unpacked.getValue()).isEqualTo(value); + } + } +} diff --git a/client/src/test/proto/spine/test/queries/client_requests.proto b/client/src/test/proto/spine/test/queries/client_requests.proto index be3e989226b..07d27919b79 100644 --- a/client/src/test/proto/spine/test/queries/client_requests.proto +++ b/client/src/test/proto/spine/test/queries/client_requests.proto @@ -28,6 +28,9 @@ option java_package="io.spine.test.client"; option java_multiple_files = true; option java_outer_classname = "ClientRequestsProto"; +import "google/protobuf/timestamp.proto"; +import "spine/core/user_id.proto"; + // Sample objects for testing Queries and Topics. // Simple ID for tests. @@ -51,6 +54,8 @@ message TestEntity { int32 third_field = 4 [(column) = true]; TestEntityName name = 5; + + TestEntityOwner owner = 6; } // A simple value holder. @@ -62,3 +67,18 @@ message TestEntityName { string value = 1 [(column) = true]; } + +// A message-typed field of the `TestEntity` which is used to test operations on nested fields. +message TestEntityOwner { + spine.core.UserId id = 1; + + google.protobuf.Timestamp when_last_visited = 2; + + Role role = 3; + + enum Role { + UNDEFINED = 0; + ADMIN = 1; + USER = 2; + } +} diff --git a/config b/config index 6c2bf59a626..2f239bb2534 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 6c2bf59a6260d7fbe1e15e4a5d8a49af24195992 +Subproject commit 2f239bb25349e117902a8a0d0b1df57d2a7d48f9 diff --git a/core/build.gradle b/core/build.gradle index 146f4f35c3c..ecf65432b7a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -26,5 +26,17 @@ dependencies { testImplementation "io.spine:spine-testutil-time:$spineTimeVersion" } +modelCompiler { + fields { + // Enable the strongly-typed fields generation for `spine.core.Event` as currently it's + // a subscribable entity state. + generateFor "spine.core.Event", markAs("io.spine.base.EntityStateField") + + // Enable the strongly-typed fields generation for `spine.core.EventContext` to allow + // creation of typed event filters based on event context. + generateFor "spine.core.EventContext", markAs("io.spine.core.EventContextField") + } +} + apply from: deps.scripts.testArtifacts apply from: deps.scripts.publishProto diff --git a/core/src/main/java/io/spine/core/EventContextField.java b/core/src/main/java/io/spine/core/EventContextField.java new file mode 100644 index 00000000000..09a1c1e6793 --- /dev/null +++ b/core/src/main/java/io/spine/core/EventContextField.java @@ -0,0 +1,18 @@ +package io.spine.core; + +import io.spine.base.Field; +import io.spine.base.SubscribableField; + +/** + * A subscribable field of an event context. + * + *

When such field is specified to a subscription filter, the {@code "context."} prefix will be + * automatically appended to the field path, allowing to distinguish between event context and + * event message filters when sending the request to a server side. + */ +public class EventContextField extends SubscribableField { + + public EventContextField(Field field) { + super(field); + } +} diff --git a/license-report.md b/license-report.md index eab9dd599d5..d0c369f13f6 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:1.4.4` +# Dependencies of `io.spine:spine-client:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -47,11 +47,11 @@ * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -183,11 +183,11 @@ * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -415,12 +415,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:33 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:03 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-core:1.4.4` +# Dependencies of `io.spine:spine-core:1.4.5` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -459,11 +459,11 @@ This report was generated on **Thu Jan 23 17:10:33 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -559,11 +559,11 @@ This report was generated on **Thu Jan 23 17:10:33 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -791,12 +791,12 @@ This report was generated on **Thu Jan 23 17:10:33 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:04 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:spine-model-assembler:1.4.4` +# Dependencies of `io.spine.tools:spine-model-assembler:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -843,11 +843,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -979,11 +979,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -1206,12 +1206,12 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:05 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:spine-model-verifier:1.4.4` +# Dependencies of `io.spine.tools:spine-model-verifier:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -1266,11 +1266,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) * **POM License: BSD 3-Clause** - [http://opensource.org/licenses/BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -1430,11 +1430,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) * **POM License: BSD 3-Clause** - [http://opensource.org/licenses/BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -1681,12 +1681,12 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:06 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-server:1.4.4` +# Dependencies of `io.spine:spine-server:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -1733,11 +1733,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -1877,11 +1877,11 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -2113,12 +2113,12 @@ This report was generated on **Thu Jan 23 17:10:34 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:35 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:07 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-client:1.4.4` +# Dependencies of `io.spine:spine-testutil-client:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -2171,11 +2171,11 @@ This report was generated on **Thu Jan 23 17:10:35 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -2359,11 +2359,11 @@ This report was generated on **Thu Jan 23 17:10:35 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -2586,12 +2586,12 @@ This report was generated on **Thu Jan 23 17:10:35 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:37 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:10 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-core:1.4.4` +# Dependencies of `io.spine:spine-testutil-core:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -2644,11 +2644,11 @@ This report was generated on **Thu Jan 23 17:10:37 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -2840,11 +2840,11 @@ This report was generated on **Thu Jan 23 17:10:37 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -3067,12 +3067,12 @@ This report was generated on **Thu Jan 23 17:10:37 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:38 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Feb 14 14:07:11 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-server:1.4.4` +# Dependencies of `io.spine:spine-testutil-server:1.4.5` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -3125,11 +3125,11 @@ This report was generated on **Thu Jan 23 17:10:38 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -3313,11 +3313,11 @@ This report was generated on **Thu Jan 23 17:10:38 EET 2020** using [Gradle-Lice * **POM Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.1 +1. **Group:** com.google.protobuf **Name:** protobuf-java-util **Version:** 3.11.3 * **Manifest Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **POM License: 3-Clause BSD License** - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -3584,4 +3584,4 @@ This report was generated on **Thu Jan 23 17:10:38 EET 2020** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jan 23 17:10:41 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Fri Feb 14 14:07:15 EET 2020** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 427278181c6..032e9e3e3ca 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-core-java -1.4.4 +1.4.5 2015 @@ -70,25 +70,25 @@ all modules and does not describe the project structure per-subproject. io.spine spine-base - 1.4.3 + 1.4.4 compile io.spine spine-time - 1.4.0 + 1.4.1 compile io.spine.tools spine-model-compiler - 1.4.3 + 1.4.4 compile io.spine.tools spine-plugin-base - 1.4.3 + 1.4.4 compile @@ -130,25 +130,25 @@ all modules and does not describe the project structure per-subproject. io.spine spine-testlib - 1.4.3 + 1.4.4 test io.spine spine-testutil-time - 1.4.0 + 1.4.1 test io.spine.tools spine-mute-logging - 1.4.3 + 1.4.4 test io.spine.tools spine-plugin-testlib - 1.4.3 + 1.4.4 test @@ -210,12 +210,12 @@ all modules and does not describe the project structure per-subproject. io.spine.tools spine-javadoc-filter - 1.4.3 + 1.4.4 io.spine.tools spine-protoc-plugin - 1.4.3 + 1.4.4 net.sourceforge.pmd diff --git a/server/src/main/java/io/spine/server/stand/EntityUpdateHandler.java b/server/src/main/java/io/spine/server/stand/EntityUpdateHandler.java index 72894f6fbdf..4524cf27315 100644 --- a/server/src/main/java/io/spine/server/stand/EntityUpdateHandler.java +++ b/server/src/main/java/io/spine/server/stand/EntityUpdateHandler.java @@ -99,7 +99,7 @@ private static EntityStateChanged asEntityEvent(EventEnvelope event) { } /** - * Checks if the event message matches the subscription filters. + * Checks if the entity state matches the subscription filters. */ private boolean stateMatches(EntityState state) { TargetFilters filters = target().getFilters(); diff --git a/server/src/test/java/io/spine/client/ClientTest.java b/server/src/test/java/io/spine/client/ClientTest.java index e73ce26e249..269fc1537aa 100644 --- a/server/src/test/java/io/spine/client/ClientTest.java +++ b/server/src/test/java/io/spine/client/ClientTest.java @@ -38,7 +38,7 @@ import java.util.List; import static com.google.common.truth.Truth.assertThat; -import static io.spine.client.Filters.eq; +import static io.spine.client.EventFilter.eq; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -100,24 +100,24 @@ class Subscriptions { void createSubscriptions() { subscriptions = new ArrayList<>(); UserId currentUser = GivenUserId.generated(); - String userField = "user"; Client client = client(); Subscription userLoggedIn = client.onBehalfOf(currentUser) .subscribeToEvent(UserLoggedIn.class) - .where(eq(userField, currentUser)) + .where(eq(UserLoggedIn.Field.user(), currentUser)) .observe((e) -> {}) .post(); Subscription userLoggedOut = client.onBehalfOf(currentUser) .subscribeToEvent(UserLoggedOut.class) - .where(eq(userField, currentUser)) + .where(eq(UserLoggedOut.Field.user(), currentUser)) .observe((e) -> {}) .post(); Subscription loginStatus = client.onBehalfOf(currentUser) .subscribeTo(LoginStatus.class) - .where(eq("user_id", currentUser.getValue())) + .where(EntityStateFilter.eq(LoginStatus.Field.userId(), + currentUser.getValue())) .observe((s) -> {}) .post(); diff --git a/server/src/test/java/io/spine/client/EventSubscriptionRequestTest.java b/server/src/test/java/io/spine/client/EventSubscriptionRequestTest.java index b5c80b91fca..25ca61ea509 100644 --- a/server/src/test/java/io/spine/client/EventSubscriptionRequestTest.java +++ b/server/src/test/java/io/spine/client/EventSubscriptionRequestTest.java @@ -38,8 +38,8 @@ import org.junit.jupiter.api.Test; import static com.google.common.truth.Truth.assertThat; -import static io.spine.client.Filters.all; -import static io.spine.client.Filters.eq; +import static io.spine.client.CompositeEventFilter.all; +import static io.spine.client.EventFilter.eq; @DisplayName("`EventSubscriptionRequest` should") class EventSubscriptionRequestTest extends AbstractClientTest { @@ -101,8 +101,10 @@ void subscribeWithFiltering() { .user(); client().asGuest() .subscribeToEvent(UserLoggedIn.class) - .where(all(eq("user", userId), - eq("context.past_message.actor_context.actor", guestUser))) + .where(all(eq(UserLoggedIn.Field.user(), userId), + eq(EventContext.Field.pastMessage() + .actorContext() + .actor(), guestUser))) .observe((e, c) -> { rememberedMessage = e; rememberedContext = c; diff --git a/server/src/test/java/io/spine/client/package-info.java b/server/src/test/java/io/spine/client/package-info.java index b21253408af..f82f611bb32 100644 --- a/server/src/test/java/io/spine/client/package-info.java +++ b/server/src/test/java/io/spine/client/package-info.java @@ -19,7 +19,7 @@ */ /** - * This package contains of high-level {@link io.spine.client.Client} API. For the tests of + * This package contains tests of high-level {@link io.spine.client.Client} API. For the tests of * low-level client API based on {@link io.spine.client.ActorRequestFactory} please see the same * package in the {@link ":client"} module. * diff --git a/version.gradle b/version.gradle index 3acac615ed4..34584f4ebdc 100644 --- a/version.gradle +++ b/version.gradle @@ -25,15 +25,15 @@ * as we want to manage the versions in a single source. */ -final def spineVersion = '1.4.4' +final def spineVersion = '1.4.5' ext { // The version of the modules in this project. versionToPublish = spineVersion // Depend on `base` for the general definitions and a model compiler. - spineBaseVersion = '1.4.3' + spineBaseVersion = spineVersion // Depend on `time` for `ZoneId`, `ZoneOffset` and other date/time types and utilities. - spineTimeVersion = '1.4.0' + spineTimeVersion = '1.4.1' }