Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@External annotation #1269

Merged
merged 12 commits into from
Apr 29, 2020
39 changes: 39 additions & 0 deletions core/src/main/java/io/spine/core/AcceptsExternal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.core;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

/**
* Marks a handler method annotation which may accept external messages.
*
* @see External
*/
@Retention(SOURCE)
@Target(ANNOTATION_TYPE)
@Documented
public @interface AcceptsExternal {
}
37 changes: 37 additions & 0 deletions core/src/main/java/io/spine/core/AcceptsFilters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.core;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

/**
* Marks a handler method annotation which supports filtering events by fields using {@link Where}.
*/
@Retention(SOURCE)
@Target(ANNOTATION_TYPE)
@Documented
public @interface AcceptsFilters {
}
46 changes: 46 additions & 0 deletions core/src/main/java/io/spine/core/External.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.core;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Marks a handler method parameter to be of an external origin.
*
* <p>External messages are messages originated in a different Bounded Context.
*
* <p>Events (including Rejections) and entity states may be external.
*
* <p>Annotate the first parameter of the handler method with {@code @External} to make the handler
* accept external messages. By default, any message handler accepts domestic messages.
*
* @see AcceptsExternal
*/
@Retention(RUNTIME)
@Target(PARAMETER)
@Documented
public @interface External {
}
51 changes: 8 additions & 43 deletions core/src/main/java/io/spine/core/Subscribe.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,50 +83,8 @@
* parameter.
* </ul>
*
* <h1>Filtering Events by a Field Value</h1>
* <p>If a {@linkplain ByField field filter} is defined, only the events matching this filter are
* passed to the subscriber.
*
* <p>Any combination of {@code external} and {@code filter} is valid, i.e. it is possible
* to filter external event subscriptions. Though, it is not possible to filter entity state
* updates.
*
* <p>A single subscribing class may define a number of subscriber methods with different field
* filters. Though, all the field filters must target the same field. For example, this event
* handling is valid:
* <pre>
*
* {@literal @Subscribe(filter = @ByField(path = "subscription.status", value = "EXPIRED"))}
* void onExpired(UserLoggedIn event) {
* // Handle expired subscription.
* }
*
* {@literal @Subscribe(filter = @ByField(path = "subscription.status", value = "INACTIVE"))}
* void onInactive(UserLoggedIn event) {
* // Handle inactive subscription.
* }
*
* {@literal @Subscribe}
* void on(UserLoggedIn event) {
* // Handle other cases.
* }
*
* </pre>
* <p>And this one is not:
* <pre>
* {@literal @Subscribe(filter = @ByField(path = "subscription.status", value = "EXPIRED"))}
* void onExpired(UserLoggedIn event) {
* }
*
* {@literal @Subscribe(filter = @ByField(path = "payment_method.status", value = "UNSET"))}
* void onUnknownBilling(UserLoggedIn event) {
* // Error, different field paths used in the same class for the same event type.
* }
* </pre>
*
* <p>If the annotation is applied to a method which doesn't satisfy either of these requirements,
* this method is not considered as a subscriber and is not registered for the command output
* delivery.
* this method is not considered a subscriber and is not registered for the command output delivery.
*
* <p>Event subscriber methods are designed to be called by the framework only.
* Therefore, it is recommended to declare a them as package-private.
Expand All @@ -135,16 +93,23 @@
* <p>Package-private access level still declares that an event reactor method is a part
* of the Bounded Context-level API. See the {@link io.spine.core.BoundedContext
* BoundedContext} description on how the packages and Bounded Contexts relate.
*
* <p>When subscribing to events, {@linkplain Where field filtering} is supported.
*/
@Retention(RUNTIME)
@Target(METHOD)
@Documented
@AcceptsFilters
@AcceptsExternal
public @interface Subscribe {

/**
* When {@code true}, the annotated method receives an event generated from outside of the
* Bounded Context to which the annotated method's class belongs.
*
* @deprecated please use {@link External @External} annotation for the first method parameter.
*/
@Deprecated
boolean external() default false;

/**
Expand Down
53 changes: 46 additions & 7 deletions core/src/main/java/io/spine/core/Where.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,57 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Filters events delivered to the {@linkplain Subscribe subscriber method} the first
* parameter of which has this annotation.
* Filters events delivered to a handler method.
*
* <p>To apply filtering to an event handler method, annotate the first parameter of the method.
*
* <p>For example, the following method would be invoked only if the owner of the created
* project is {@code [email protected]}:
* <pre>{@code
* \@Subscribe
* <pre>
*{@literal @Subscribe }
* void on(@Where(field = "owner.email", equals = "[email protected]") ProjectCreated e) { ... }
* }</pre>
* </pre>
*
* <p>Annotations for handler methods which support {@code @Where} should be marked with
* {@link AcceptsFilters}.
*
* <h1>Filtering Events by a Field Value</h1>
* <p>If a field filter is defined, only the events matching this filter are passed to the handler
* method.
*
* <p>A single class may define a number of handler methods with different field filters. Though,
* all the field filters must target the same field. For example, this event handling is valid:
* <pre>
* {@literal @Subscribe}
* void{@literal onExpired(@Where(field = "subscription.status", equals = "EXPIRED")
* UserLoggedIn event)} {
* // Handle expired subscription.
* }
*
* {@literal @Subscribe}
* void{@literal onInactive(@Where(field = "subscription.status", equals = "INACTIVE")
* UserLoggedIn event)} {
* // Handle inactive subscription.
* }
*
* {@literal @Subscribe}
* void on(UserLoggedIn event) {
* // Handle other cases.
* }
* </pre>
* <p>And this one is not:
* <pre>
* {@literal @Subscribe}
* void{@literal onExpired(@Where(field = "subscription.status", equals = "EXPIRED")
* UserLoggedIn event)} {
* }
*
* <p><em>NOTE:</em> This syntax applies only to events. Filtering state subscriptions
* is not supported.
* {@literal @Subscribe}
* void{@literal onUnknownBilling(@Where(field = "payment_method.status", equals = "UNSET")
* UserLoggedIn event)} {
* // Error, different field paths used in the same class for the same event type.
* }
* </pre>
*/
@Retention(RUNTIME)
@Target(PARAMETER)
Expand Down
Loading