All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.client.EReplyFeed Maven / Gradle / Ivy

The newest version!
//
// Copyright 2015, 2016, 2019, 2020 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package net.sf.eBus.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.ERequestFeed.RequestState;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.EReplyMessage.ReplyStatus;
import net.sf.eBus.messages.ERequestMessage;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.util.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@code EReplyFeed} is the application entry point for posting
 * replies to {@link ERequestMessage request messages} to
 * requestors. Follow these steps to use this feed:
 * 

* Step 1: Implement the {@link EReplier} * interface. *

*

* Step 2: * {@link EReplyFeed.Builder Build}a reply feed for a given * {@code EReplier} instance and * {@link EMessageKey type+topic message key}. The condition is * optional and may be {@code null}. If provided, then only * request messages satisfying the condition are forwarded to the * replier. *

*

* Use * {@link Builder#cancelRequestCallback(CancelRequestCallback)} * and * {@link Builder#requestCallback(RequestCallback)} to set Java * lambda expressions used in place of {@link EReplier} interface * methods. *

* Step 3: {@link #advertise() Advertise} this * replier to eBus. This allows eBus to match repliers with * requestors. *

*

* Step 4: Wait for * {@link EReplier#request(EReplyFeed.ERequest) requests} * to arrive. Keep the given {@link ERequest} instance handy * because {@code ERequest} is used to post reply messages back * to the requestor, not {@code EReplyFeed}. *

*

* Step 5: * {@link EReplyFeed.ERequest#reply(EReplyMessage) Send} one or * more {@link EReplyMessage reply} messages back to the * requestor. *

*

* Step 6: When the replier is shutting down, * {@link #unadvertise() retract} the reply advertisement and * {@link #close() close} the feed. *

*

Example use of EReplyFeed

*
import java.util.ArrayList;
import java.util.List;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EReplier;
import net.sf.eBus.client.EReplyFeed;
import net.sf.eBus.messages.EReplyMessage
import net.sf.eBus.messages.EReplyMessage.ReplyStatus; import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ERequestMessage;

Step 1: Implement EReplier interface.
public class CatalogReplier implements EReplier {
    private final EMessageKey mKey;
    private final FeedScope mScope;
    private EReplyFeed mFeed;
    private final List<EReplyFeed.ERequest> mRequests;

    public CatalogReplier(final String subject, final FeedScope scope) {
        mKey = new EMessageKey(com.acme.CatalogOrder.class, subject);
        mScope = scope;
        mRequests = new ArrayList<>();
    }

    @Override
    public void startup() {
        try {
            Step 2: Open a reply feed for reply message key. May override EReplier interfaces methods.
            // This advertisement has no associated ECondition and uses EReplier interface method overrides.
            mFeed = (EReplyFeed.builder()).target(this)
                                          .messageKey(mKey)
                                          .scope(mScope)
                                          .build();

            Step 3: Advertise reply feed.
            mFeed.advertise();

            mFeed.updateFeedState(EFeedState.UP);
        } catch (IllegalArgumentException argex) {
            // Advertisement failed. Place recovery code here.
        }
    }

    Step 4: Wait for requests to arrive.
    @Override
    public void request(final EReplyFeed.ERequest request, final EReplyFeed feed) {
        final ERequestMessage msg = request.request();

        try {
            mRequests.add(request);
            startOrderProcessing(msg, request);
        } catch (Exception jex) {
            request.reply(new CatalogOrderReply(ReplyStatus.ERROR, // reply status.
                                                 jex.getMessage())); // reply reason.
        }
    }

    @Override
    public void cancelRequest(final EReplyFeed.ERequest request, final boolean mayRespond) {
        // Is this request still active? It is if the request is listed.
        if (mRequests.remove(request)) {
            // Yes, try to stop the request processing.
            try {
                // Throws an exception if the request cannot be stopped.
                stopOrderProcessing(request);
            } catch (Exception jex) {
                // Ignore since nothing else can be done.
            }
        }
    }

    Step 5: Send one or more reply messages back to requestor.
    public void orderReply(final EReplyFeed.ERequest request, final ReplyStatus status, final String reason) {
        final ERequestAd ad = mRequests.get(request);

        if (mRequests.contains(request) && request.isActive()) {
            request.reply((OrderReply.builder()).subject(mKey.subject())
                                                .timestamp(Instant.now())
                                                .replyStatus(status)
                                                .replyReason(reason)
                                                .build());

            // If the request processing is complete, remove the request.
            if (status.isFinal()) {
                mRequests.remove(request);
            }
        }
    }

    @Override
    public void shutdown() {
        final String subject = mKey.subject();

        // While eBus will does this for us, it is better to do it ourselves.
        for (EReplyFeed.ERequest request : mRequests) {
            request.reply((EReplyMessage.builder()).subject(subject)
                                                   .timestamp(Instant.now())
                                                   .replyStatus(ReplyStatus.ERROR)
                                                   .replyReason("shutting down")
                                                   .build());
        }

        mRequests.clear();

        Step 6: When shutting down, either unadvertise or close reply feed.
        if (mFeed != null) {
            mFeed.close();
            mFeed = null;
        }
    }
}
* * @see EReplier * @see ERequestor * @see ERequestFeed * * @author Charles Rapp */ public final class EReplyFeed extends ESingleFeed implements IEReplyFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link EReplier#request(EReplyFeed.ERequest)} method name. */ /* package */ static final String REQUEST_METHOD = "request"; /** * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)} * method name. */ /* package */ static final String CANCEL_METHOD = "cancelRequest"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = LoggerFactory.getLogger(EReplyFeed.class); //----------------------------------------------------------- // Locals. // /** * Use this condition to check if the request message should * be forwarded to subscriber. */ private final ECondition mCondition; /** * The active requests accepted by this reply feed. */ private final List mRequests; /** * The request message data type. Used to determine if reply * messages belong to the * {@link net.sf.eBus.messages.EReplyInfo} allowed message * types. */ private final MessageType mDataType; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then defaults * to * {@link EReplier#request(EReplyFeed.ERequest)}. */ private final RequestCallback mRequestCallback; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then defaults * to * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)}. */ private final CancelRequestCallback mCancelCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new reply feed based on the builder * configuration. Note that the builder configuration is * guaranteed to be valid prior to calling this constructor. * @param builder contains valid reply feed configuration. */ private EReplyFeed(final Builder builder) { super (builder); mCondition = builder.mCondition; mRequests = new ArrayList<>(); mDataType = builder.getMessageType(); mRequestCallback = builder.mRequestCallback; mCancelCallback = builder.mCancelCallback; } // end of EReplyFeed(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // EFeed Interface Implementation. // /** * If the advertisement is in place, then un-advertises it, * sends a final error reply to all extant requests, and if * this feed's scope is local & remote or remote only, * removes this feed from the advertisers list. * * @see EFeed#close() */ @Override protected void inactivate() { if (mInPlace) { ((ERequestSubject) mSubject).unadvertise(this); mInPlace = false; // Fail all extant requests since this replier is no // longer in business. failRequests("replier closed"); } } // end of inactivate() /** * Always returns zero because reply feeds do no have the * concept of activation. * @param loc contra-feed location. * @param fs contra-feed state. * @return zero. */ @Override /* package */ int updateActivation(final ClientLocation loc, final EFeedState fs) { return (0); } // end of updateActivation(ClientLocation, EFeedState) // // end of EFeed Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if this reply feed is both open and * advertised; otherwise, returns {@code false}. * @return {@code true} if this reply feed is open and * advertised. */ @Override public boolean isAdvertised() { return (mIsActive.get() && mInPlace); } // end of isAdvertised() /** * Returns {@code true} if {@code repKey} is an allowed * reply message for this request and {@code false} if * not. A reply which is not allowed for the request will not * be sent to the requester. * @param repKey the reply message key. * @return {@code true} if the reply message key is allowed * for this request. */ /* package */ boolean isValidReply(final EMessageKey repKey) { return (mDataType.isValidReply(repKey.messageClass())); } // end of isValidReply(EMessageKey) // // end of Get Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Updates the replier feed state to the given value. If * {@code update} equals the currently stored reply feed * state, nothing is done. Otherwise, the updated value is * stored. If this feed is advertised to the server and * the subscription feed is up, then this update is forwarded * to subscribed requestors. * @param update the new reply feed state. * @throws NullPointerException * if {@code update} is {@code null}. * @throws IllegalStateException * if this feed was closed and is inactive or is not * advertised. */ @Override public void updateFeedState(final EFeedState update) { Objects.requireNonNull(update, "update is null"); if (!mIsActive.get()) { throw (new IllegalStateException(FEED_IS_INACTIVE)); } if (!mInPlace) { throw ( new IllegalStateException(FEED_NOT_ADVERTISED)); } // Does this update actually change anything? if (update != mFeedState) { // Yes. Apply the update. mFeedState = update; sLogger.debug( "{} replier {}, feed {}: setting {} feed state to {} ({}).", mEClient.location(), mEClient.clientId(), mFeedId, key(), update, mScope); // Forward the update to the subject. ((ERequestSubject) mSubject).updateFeedState(this); } // No change. Nothing to do. } // end of updateFeedState(EFeedState) /** * Forwards the given request message and request object to * the client, returning a {@link ERequest} object. Note that * {@code checkRequest(ERequestMessage, EClient.ClientLocation)} * was previously called to determine if the replier accepts * the request message. * {@link ERequestSubject} can determine if any repliers * accepted the request. If no repliers accepted or * this reply is inactive, then returns {@code null}. * @param request the request associated with the * message. * @param message the request message to be posted to replier * clients. * @return request instance linked to * {@link ERequestFeed.ERequest}. May be {@code null}. */ /* package */ EReplyFeed.ERequest request(final ERequestFeed.ERequest request, final ERequestMessage message) { EReplyFeed.ERequest retval; // Is this feed still alive? // Is this feed up? if (!mIsActive.get() || mFeedState == EFeedState.DOWN) { // No, this feed is gone or down. retval = null; } else { // Yes. Add the request to the active requests list // BUT DO NOT DISPATCH THE REQUEST TO THE REPLIER // CLIENT. // The bookkeeping needed to process the reply is // not yet in place. retval = new EReplyFeed.ERequest(request, this, message, mCondition, mCancelCallback); synchronized (mRequests) { mRequests.add(retval); } } return (retval); } // end of request(ERequest, ERequestMessage) // // end of Set Methods. //----------------------------------------------------------- /** * Returns a new {@code EReplyFeed} builder instance. This * instance should be used to build a single reply feed * instance and not used to create multiple such feeds. * @return new reply feed builder. */ public static Builder builder() { return (new Builder()); } // end of builder() /** * Advertises this replier fed to the associated request * subject. If this feed is currently advertised to the * subject, then does nothing. * @throws IllegalStateException * if this feed is closed or if the client did not override * {@link EReplier} methods nor put the required callbacks in * place. * * @see #unadvertise() * @see #updateFeedState(EFeedState) * @see EFeed#close() */ @Override public void advertise() { if (!mIsActive.get()) { throw (new IllegalStateException(FEED_IS_INACTIVE)); } if (!mInPlace) { sLogger.debug( "{} replier {}, Feed {}: advertising {} ({}).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope); ((ERequestSubject) mSubject).advertise(this); mInPlace = true; } } // end of subscribe() /** * Retracts this replier feed from the associated request * subject. Does nothing if this feed is not currently * advertised or closed. * @throws IllegalStateException * if this feed was closed and is inactive. * * @see #advertise() * @see EFeed#close() */ @Override public void unadvertise() { if (!mIsActive.get()) { throw (new IllegalStateException(FEED_IS_INACTIVE)); } if (mInPlace) { sLogger.debug( "{} replier {}, Feed {}: unadvertising ({}).", mEClient.location(), mEClient.clientId(), mFeedId, mScope); ((ERequestSubject) mSubject).unadvertise(this); mInPlace = false; mActivationCount = 0; mFeedState = EFeedState.DOWN; // Fail all extant requests since this replier is no // longer in business. failRequests("replier unadvertised"); } } // end of unsubscribe() /** * Dispatch {@code request} to the replier client. * @param request post this request to the replier. */ /* package */ void dispatch(final EReplyFeed.ERequest request) { mEClient.dispatch( new RequestTask(request, mRequestCallback)); } // end of dispatch(EReplyFeed.ERequest) /** * Removes the completed request from the requests list. * @param request the newly completed request. */ /* package */ void requestDone(final ERequest request) { synchronized (mRequests) { mRequests.remove(request); } } // end of requestDone(ERequest) /** * Posts an error reply containing the given reason to each * outstanding requests and then clears the request list. * @param reason reason why the request is being rejected. */ private void failRequests(final String reason) { final List requests = new ArrayList<>(); // Clear out the now defunct requests and work off of the // copy. synchronized (mRequests) { requests.addAll(mRequests); mRequests.clear(); } requests.forEach( request -> { final EReplyMessage.Builder builder = EReplyMessage.builder(); request.reply(builder.subject((request.key()).subject()) .replyStatus(ReplyStatus.ERROR) .replyReason(reason) .build()); }); } // end of failRequests(String) //--------------------------------------------------------------- // Inner classes. // /** * {@code EReplyFeed.Builder} is now the preferred * mechanism for creating a {@code EReplyFeed} instance. * A {@code Builder} instance is acquired from * {@link EReplyFeed#builder()}. The following example shows * how to create a {@code EReplyFeed} instance using a * {@code Builder}. The code shows how {@code EReplier} * interface method * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)} * and * {@link EReplier#request(EReplyFeed.ERequest)} methods may * be replaced with lambda expressions. *
@Override public void startup() {
    final EMessageKey key = new EMessageKey(com.acme.CatalogOrder.class, subject);
    final EReplyFeed feed = (EReplyFeed.builder()).target(this)
                                                  .messageKey(key)
                                                  .scope(EFeed.FeedScope.REMOTE_ONLY)
                                                  .condition(m → ((CatalogOrder) m).category == Category.APPLIANCES)
                                                  .cancelRequest(this::cancelOrder)
                                                  .requestCallback(this::newOrder)
                                                  .build();
    ...
}
* * @see #builder() */ public static final class Builder extends ESingleFeed.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Use this condition to check if the request message * should be forwarded to replier. Default setting is * {@link #NO_CONDITION}. */ private ECondition mCondition; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then * defaults to * {@link EReplier#request(EReplyFeed.ERequest)}. */ private RequestCallback mRequestCallback; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then * defaults to * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)}. */ private CancelRequestCallback mCancelCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Private method since a {@code Builder} instance may * only be instantiated by {@link #builder()}. * * @see #builder() */ private Builder() { super (FeedType.REPLY_FEED, EReplyFeed.class); mCondition = NO_CONDITION; } // end of Builder() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Method Overrides. // /** * Returns eBus subject for the configured message key. * @return eBus feed subject. */ @Override protected ESubject getSubject() { return (ERequestSubject.findOrCreate(mKey)); } // end of getSubject() /** * Checks that both request and cancel request callbacks * are defined. * @param problems place invalid configuration settings * in this problems list. * @return {@code problems} to allow for method chaining. */ @Override protected Validator validate(final Validator problems) { // If the callbacks were not put in place, then use // the defaults. if (mRequestCallback == null && mTarget != null && isOverridden(REQUEST_METHOD, EReplyFeed.ERequest.class)) { mRequestCallback = ((EReplier) mTarget)::request; } if (mCancelCallback == null && mTarget != null && isOverridden(CANCEL_METHOD, EReplyFeed.ERequest.class, boolean.class)) { mCancelCallback = ((EReplier) mTarget)::cancelRequest; } return (super.validate(problems) .requireTrue((mKey != null && mKey.isRequest()), "messageKey", "messageKey is not a request") .requireTrue((!mKey.isLocalOnly() || mScope == FeedScope.LOCAL_ONLY), "messageKey", String.format( "%s is local-only but feed scope is %s", mKey, mScope)) .requireNotNull(mRequestCallback, "requestCallback") .requireNotNull(mCancelCallback, "cancelCallback")); } // end of validate(Validator) /** * Returns a new {@code EReplyFeed} instance based on * {@code this Builder}'s configuration. * @return new reply feed. */ @Override protected EReplyFeed buildImpl() { return (new EReplyFeed(this)); } // end of buildImpl() /** * Returns {@code this} reference. * @return {@code this} reference. */ @Override protected Builder self() { return (this); } // end of self() // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns message type associated with the configured * message key. * @return request message type. */ private MessageType getMessageType() { return ( (MessageType) DataType.findType( mKey.messageClass())); } // end of getMessageType() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets advertisement condition to the given value. May * be {@code null} which results in condition being set * to {@link #NO_CONDITION}. *

* An example using this method is: *

* {@code condition(m -> ((CatalogUpdate) m).category == Category.APPLIANCES)} * @param condition advertisement condition. * @return {@code this Builder} instance. */ public Builder condition(final ECondition condition) { if (condition == null) { mCondition = NO_CONDITION; } else { mCondition = condition; } return (this); } // end of condition(ECondition) /** * Puts the new request callback in place. If {@code cb} * is not {@code null}, requests will be passed to * {@code cb} rather than * {@link EReplier#request(EReplyFeed.ERequest)}. A * {@code null cb} means that requests are passed to the * {@link EReplier#request(EReplyFeed.ERequest)} * override. *

* An example using this method is: *

* {@code requestCallback(this::newOrder)} * @param cb the request callback. May be {@code null}. * @return {@code this Builder} instance. */ public Builder requestCallback(final RequestCallback cb) { mRequestCallback = cb; return (this); } // end of requestCallback(RequestCallback) /** * Puts the cancel request callback in place. If * {@code cb} is not {@code null}, requests will be * passed to {@code cb} rather than * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)}. * A {@code null cb} means that cancellations are passed * to the * {@link EReplier#cancelRequest(EReplyFeed.ERequest, boolean)} * override. *

* An example using this method is: *

* {@code this::cancelOrder} * @param cb the cancel request callback. May be * {@code null}. * @return {@code this Builder} instance. */ public Builder cancelRequestCallback(final CancelRequestCallback cb) { mCancelCallback = cb; return (this); } // end of cancelRequestCallback(CancelRequestCallback) // // end of Set Methods. //------------------------------------------------------- } // end of class Builder /** * {@code ERequest} is responsible for matching an * {@link ERequestFeed.ERequest} with a single * {@link EReplyFeed}. The replier uses * {@link ERequest#reply(EReplyMessage)} to post a * {@link EReplyMessage reply message} back to the requesting * client. Replies cannot be sent using {@link EReplyFeed} * nor can {@code ERequest} instances be retrieved from * {@code EReplyFeed}. It is the application's responsibility * to track active {@code ERequest} instances in order to * send reply messages back to requestors. *

* ({@code EReplyFeed} does track the active {@code ERequest} * instances but for internal purposes only. The reason is * due to {@code ERequest} not having the original request * message. Without the request message, an {@code ERequest} * object is unusable by the application.) *

*

* A {@link ERequestor request client} does not directly * interact with {@code ERequest} instances but with its * {@link ERequestFeed.ERequest}. This is because there is a * one-to-one * relationship between {@code ERequestFeed.ERequest} and * {@code EReplyFeed.ERequest}. *

*

* Each request is given an identifier that is unique for the * request lifespan. Once the request reaches the * {@link RequestState#DONE done} or * {@link RequestState#CANCELED canceled} state, this * identifier is returned an identifier pool for re-use. It * is likely that a later request will be assigned the same * identifier. *

* * @see ERequestFeed * @see ERequestFeed.ERequest * @see EReplyFeed * @see ERequestMessage * @see EReplyMessage */ public static final class ERequest extends ESingleFeed { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Post reply messages and cancel replies to this request * feed. The requestor tracks the request state. */ private final ERequestFeed.ERequest mRequestor; /** * Mark replies as coming from this feed. The request * feed needs to know from where replies originate. */ private final EReplyFeed mReplier; /** * The request message itself. */ private final ERequestMessage mRequest; /** * Apply {@link #mRequest} to this condition and deliver * request if-and-only-if the message satisfies the * condition. */ private final ECondition mCondition; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then * defaults to * {@link EReplier#cancelRequest(EReplyFeed.ERequest)}. */ private final CancelRequestCallback mCancelCallback; /** * Tracks whether this request for its replier is active, * cancel in-progress, or done. Initialize to active. */ private RequestState mRequestState; /** * Tracks the number of remaining repliers for this request. * For a local replier, this will always be one. For a remote * replier, this may be > one. */ private int mRemaining; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new request instance which links a reply and * a request feed. * @param reqFeed forward replies to this requestor. * @param repFeed replies come from this feed. * @param request the request message. * @param condition request condition. * @param cb request cancellation callback. */ /* package */ ERequest(final ERequestFeed.ERequest reqFeed, final EReplyFeed repFeed, final ERequestMessage request, final ECondition condition, final CancelRequestCallback cb) { super (repFeed.mEClient, repFeed.mScope, repFeed.mFeedType, repFeed.mSubject); mRequestor = reqFeed; mReplier = repFeed; mRequest = request; mCondition = condition; mCancelCallback = cb; mRequestState = RequestState.ACTIVE; // Assume local until updated. mRemaining = 1; } // end of ERequest(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // EFeed Abstract Method Overrides. // /** * Cancels this request if still active. */ @Override protected void inactivate() { if (mRequestState == RequestState.ACTIVE) { state(RequestState.CANCELED); // Post this request cancellation to the replier. (mReplier.mEClient).dispatch( new CancelRequestTask( this, false, mReplier, mCancelCallback)); } } // end of inactivate() @Override /* package */ int updateActivation(final ClientLocation loc, final EFeedState fs) { return (0); } // end of updateActivation(ClientLocation, EFeedState) // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return ((mRequest.key()).toString()); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the associated request message. * @return request message. */ public ERequestMessage request() { return (mRequest); } // end of request() /** * Returns associated request message condition. * @return message condition. */ public ECondition condition() { return (mCondition); } // end of condition() /** * Returns the current request state. * @return the request state. */ public RequestState state() { return (mRequestState); } // end of state() /** * Returns the associated reply feed. * @return a reply feed. */ public EReplyFeed replier() { return (mReplier); } // end of replier() /** * Returns the remote replier count for this request. * @return number of remaining remote repliers. */ /* package */ int remaining() { return (mRemaining); } // end of remaining() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets the request state to the given value. * @param state the latest request state. */ /* package */ void state(final RequestState state) { sLogger.debug("Reply {}.{}: setting state to {}.", mReplier.feedId(), mFeedId, state); mRequestState = state; // If the new state means that the request is // completed (whether successfully or not), remove // this request from the reply feed and close this // feed. if (state == RequestState.DONE || state == RequestState.CANCELED) { mReplier.requestDone(this); close(); } } // end of state(RequestState) /** * Sets the number of remaining remote repliers for this * request. * @param n remote replier count. */ /* package */ void remoteRemaining(final int n) { sLogger.debug( "Reply {}.{}: number of {} repliers remaining is {}.", mReplier.feedId(), mFeedId, mReplier.location(), n); // Have the request feed update its remaining count. mRequestor.updateRemaining(mRemaining, n); mRemaining = n; } // end of remoteRemaining(int) // // end of Set Methods. //------------------------------------------------------- //------------------------------------------------------- // Local Client API. // Applications should use the following methods to send // a request or a reply to a request. // /** * Forwards the reply message to the request feed. If * this is a final reply (whether OK or error), the * request state is set to done. * @param msg the reply message. * @throws IllegalArgumentException * if {@code msg} is {@code null} or is not a valid reply * message class for this request. * @throws IllegalStateException * if the request state is {@link RequestState#DONE done}. */ public void reply(final EReplyMessage msg) { // Is there a message to send? if (msg == null) { throw ( new IllegalArgumentException("msg is null")); } // Is this reply message valid for the given request? if (!(mReplier.isValidReply(msg.key()))) { // No, wrong message type. throw ( new IllegalArgumentException( (msg.key()).className() + " is not a valid reply message class for " + (mRequest.key()).className())); } if (!(((msg.key()).subject()).equals( (mSubject.key()).subject()))) { throw ( new IllegalArgumentException( "reply subject \"" + (msg.key()).subject() + "\" does not match request message subject \"" + (mSubject.key()).subject() + "\"")); } // Is this request still in-progress? if (mRequestState == RequestState.DONE) { throw ( new IllegalStateException( "request is done")); } // Everything checks out. Is this request finished? if (msg.isFinal() && (mRemaining - 1) == 0) { // Yes. Mark that done. --mRemaining; state(RequestState.DONE); } if (sLogger.isTraceEnabled()) { sLogger.trace( "Reply {}.{}: forwarding reply message:\n{}", mReplier.feedId(), mFeedId, msg); } else { sLogger.debug( "Reply {}.{}: forwarding reply message {}.", mReplier.feedId(), mFeedId, msg.key()); } // Now hand this reply off to the originating request // feed and that feed will forward it to the client. mRequestor.reply(mRemaining, msg, this); } // end of reply(EReplyMessage) /** * Posts an respond-able cancel request to replier. * @return {@code true} if the request is still active * with this replier and {@code false} if not. */ /* package */ boolean cancel() { final boolean retcode = (mRequestState == RequestState.ACTIVE); if (retcode) { // Post this request cancellation to the replier. (mReplier.mEClient).dispatch( new CancelRequestTask( this, true, mReplier, mCancelCallback)); } return (retcode); } // end of cancel() /** * Forwards a remote reply message to the request feed. * If there are no more remote replies, the request state * is set to done. * @param msg the reply message. */ /* package */ void remoteReply(final EReplyMessage msg) { // Did the remote eBus previously report no more // replies? if (mRemaining == 0) { state(RequestState.DONE); } if (sLogger.isTraceEnabled()) { sLogger.trace( "Reply {}.{}: forwarding reply message:\n{}", mReplier.feedId(), mFeedId, msg); } else { sLogger.debug("Reply {}.{}: forwarding {}.", mReplier.feedId(), mFeedId, msg.key()); } // Now hand this reply off to the originating request // feed and that feed will forward it to the client. mRequestor.reply(mRemaining, msg, this); } // end of remoteReply(EReplyMessage) // // end of Local Client API. //------------------------------------------------------- } // end of class ERequest /** * This task forwards an eBus request to * {@link EReplier#request(EReplyFeed.ERequest)}. */ private static final class RequestTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Pass the request to the application via this callback. */ private final RequestCallback mCallback; //--------------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new request task for the given message and * feed. * @param request the eBus request instance. Post replies * to {@link ERequest#reply(EReplyMessage)}. * @param cb method callback. */ private RequestTask(final EReplyFeed.ERequest request, final RequestCallback cb) { super (request); mCallback = cb; } // end of RequestTask(ERequest, RequestCallback) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementations. // /** * Passes the arguments to * {@link EReplier#request(EReplyFeed.ERequest)}, logging * any client-thrown exception. */ @Override public void run() { final Object target = (mFeed.eClient()).target(); sLogger.trace("{}", this); if (target != null) { try { final EReplyFeed.ERequest request = (EReplyFeed.ERequest) mFeed; final ECondition condition = request.condition(); final ERequestMessage message = request.request(); // Is this replier interested in this // request? if (condition.test(message)) { // Yes. Forward request message to // replier. mCallback.call(request); } } catch (Throwable tex) { final String reason = "RequestTask[{}, {}] exception"; final String className = (target.getClass()).getName(); if (sLogger.isDebugEnabled()) { sLogger.warn( reason, className, mFeed, tex); } else { sLogger.warn(reason, className, mFeed); } } } } // end of run() // // end of Runnable Interface Implementations. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return (String.format("RequestTask[{}]", mFeed)); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- } // end of class RequestTask /** * This task forwards a cancel request to * {@link EReplier#cancelRequest(net.sf.eBus.client.EReplyFeed.ERequest, boolean)}. */ private static final class CancelRequestTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Canceling this request. */ private final EReplyFeed.ERequest mRequest; /** * Set to {@code true} if replier may respond to this * cancel request and {@code false} if this is a * unilateral request cancellation. */ private final boolean mMayRespond; /** * Pass the request cancellation via this callback. */ private final CancelRequestCallback mCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new CancelRequestTask instance for the given * request identifier. * @param request canceling this request. * @param respondFlag {@code true} if replier may respond * to this request cancellation. * @param feed the canceled request applies to this feed. * @param cb the method callback. */ private CancelRequestTask(final EReplyFeed.ERequest request, final boolean respondFlag, final EReplyFeed feed, final CancelRequestCallback cb) { super (feed); mRequest = request; mMayRespond = respondFlag; mCallback = cb; } // end of CancelRequestTask(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementations. // /** * Passes the arguments to the * {@link EReplier#cancelRequest(EReplyFeed.ERequest)}, * logging any client-thrown exception. */ @SuppressWarnings({"java:S1181"}) @Override public void run() { final Object target = (mFeed.eClient()).target(); sLogger.trace("{}", this); if (target != null) { try { mCallback.call(mRequest, mMayRespond); } catch (Throwable tex) { final String reason = "EReplier.cancelRequest({}) exception"; if (sLogger.isDebugEnabled()) { sLogger.warn(reason, mFeed, tex); } else { sLogger.warn(reason, mFeed); } } } } // end of run() // // end of Runnable Interface Implementations. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return ("CancelRequestTask"); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- } // end of class CancelRequestTask } // end of class EReplyFeed




© 2015 - 2024 Weber Informatics LLC | Privacy Policy