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

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

//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright 2015, 2016. Charles W. Rapp
// All Rights Reserved.
//

package net.sf.eBus.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
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;

/**
 * {@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 #open(EReplier, EMessageKey, EFeed.FeedScope, ECondition) Open} * 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. *

*

* Step 3 (optional): Do not override * {@link EReplier} interface methods. Instead, set callbacks * using {@link #requestCallback(RequestCallback)} and/or * {@link #cancelRequestCallback(CancelRequestCallback)} passing * in Java lambda expressions. *

*

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

*

* Step 5: 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 6: * {@link EReplyFeed.ERequest#reply(EReplyMessage) Send} one or * more {@link EReplyMessage reply} messages back to the * requestor. *

*

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

*

Example use of {@code 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 {
    public CatalogReplier(final String subject, final FeedScope scope) {
        mKey = new EMessageKey(com.acme.CatalogOrder.class, subject); mScope = scope;
        mFeed = null;
        mRequests = new ArrayList<>();
    }

    @Override
    public void startup() {
        try {
            Step 2: Open a reply feed for reply message key.
            // This advertisement has no associated ECondition; passing in null.
            mFeed = EReplyFeed.open(this, mKey, mScope, null);

            Step 3: EReplier interface methods overridden.

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

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

    Step 5: 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 EReplyFeed feed) {
        // 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 6: Send one or more reply messages back to requestor.
    public void orderReply(final EReplyFeed.ERequest request, final boolean status, final String reason) {
        final ERequestAd ad = mRequests.get(request);

        if (mRequests.contains(request) && request.isActive()) {
            request.reply(new OrderReply(status, reason), ad);

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

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

        // While eBus will does this for us, it is better to do it ourselves.
        for (EReplyFeed.ERequest request : mRequests) {
            reply = new EReplyMessage(subject, ReplyStatus.ERROR, "shutting down");
            request.reply(reply);
        }

        mRequests.clear();

        Step 7: When shutting down, either unadvertise or close reply feed.
        if (mFeed != null) {
            mFeed.close();
            mFeed = null;
        }
    }

    private final EMessageKey mKey;
    private final FeedScope mScope;
    private EReplyFeed mFeed;
    private final List<EReplyFeed.ERequest> mRequests;
}
* * @see EReplier * @see ERequestor * @see ERequestFeed * * @author Charles Rapp */ public final class EReplyFeed extends ESingleFeed implements IEReplyFeed { //--------------------------------------------------------------- // Inner classes. // /** * {@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 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 cb request cancellation callback. */ /* package */ ERequest(final ERequestFeed.ERequest reqFeed, final EReplyFeed repFeed, final ERequestMessage request, final CancelRequestCallback cb) { super (repFeed.mEClient, repFeed.mScope, repFeed.mFeedType, repFeed.mSubject); _requestor = reqFeed; _replier = repFeed; _request = request; _cancelCallback = cb; _requestState = RequestState.ACTIVE; // Assume local until updated. _remaining = 1; } // end of ERequest(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // EFeed Abstract Method Overrides. // /** * Cancels this request if still active. */ @Override protected void inactivate() { if (_requestState == RequestState.ACTIVE) { state(RequestState.CANCELED); // Post this request cancellation to the replier. (_replier.mEClient).dispatch( new CancelRequestTask( this, _replier, _cancelCallback)); } return; } // 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 ((_request.key()).toString()); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the associated request message. * @return request message. */ public ERequestMessage request() { return (_request); } // end of request() /** * Returns the current request state. * @return the request state. */ public RequestState state() { return (_requestState); } // end of state() /** * Returns the associated reply feed. * @return a reply feed. */ public EReplyFeed replier() { return (_replier); } // end of replier() /** * Returns the associated request feed. * @return request feed. */ /* package */ ERequestFeed.ERequest requestor() { return (_requestor); } // end of requestor() /** * Returns the remote replier count for this request. * @return number of remaining remote repliers. */ /* package */ int remaining() { return (_remaining); } // 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) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("Reply %d.%d: setting state to %s.", _replier.feedId(), mFeedId, state)); } _requestState = 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) { _replier.requestDone(this); close(); } return; } // 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) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("Reply %d.%d: number of %s repliers remaining is %,d.", _replier.feedId(), mFeedId, _replier.location(), n)); } // Have the request feed update its remaining count. _requestor.updateRemaining(_remaining, n); _remaining = n; return; } // 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) throws IllegalArgumentException, IllegalStateException { // 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 (!(_replier.isValidReply(msg.key()))) { // No, wrong message type. throw ( new IllegalArgumentException( ((msg.key()).messageClass()).getName() + " is not a valid reply message class for " + ((_request.key()).messageClass()).getName())); } 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 (_requestState == RequestState.DONE) { throw ( new IllegalStateException( "request is done")); } // Everything checks out. Is this request finished? if (msg.isFinal() && (_remaining - 1) == 0) { // Yes. Mark that done. --_remaining; state(RequestState.DONE); } if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format("Reply %d.%d: forwarding reply message:%n%s", _replier.feedId(), mFeedId, msg)); } else if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("Reply %d.%d: forwarding reply message %s.", _replier.feedId(), mFeedId, msg.key())); } // Now hand this reply off to the originating request // feed and that feed will forward it to the client. _requestor.reply(_remaining, msg, this); return; } // end of reply(EReplyMessage) /** * 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 (_remaining == 0) { state(RequestState.DONE); } if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format("Reply %d.%d: forwarding reply message:%n%s", _replier.feedId(), mFeedId, msg)); } else if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("Reply %d.%d: forwarding %s.", _replier.feedId(), mFeedId, msg.key())); } // Now hand this reply off to the originating request // feed and that feed will forward it to the client. _requestor.reply(_remaining, msg, this); return; } // end of remoteReply(EReplyMessage) // // end of Local Client API. //------------------------------------------------------- //----------------------------------------------------------- // Member data. // /** * Post reply messages and cancel replies to this request * feed. The requestor tracks the request state. */ private final ERequestFeed.ERequest _requestor; /** * Mark replies as coming from this feed. The request * feed needs to know from where replies originate. */ private final EReplyFeed _replier; /** * The request message itself. */ private final ERequestMessage _request; /** * 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 _cancelCallback; /** * Tracks whether this request for its replier is active, * cancel in-progress, or done. Initialize to active. */ private RequestState _requestState; /** * 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 _remaining; } // 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 _callback; //--------------------------------------------------------------- // 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); _callback = 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(); if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest(this.toString()); } if (target != null) { try { _callback.call((EReplyFeed.ERequest) mFeed); } catch (Throwable tex) { final String reason = String.format( "RequestTask[%s, %s] exception", (target.getClass()).getName(), mFeed); if (sLogger.isLoggable(Level.FINE)) { sLogger.log(Level.WARNING, reason, tex); } else { sLogger.log(Level.WARNING, reason); } } } return; } // end of run() // // end of Runnable Interface Implementations. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return (String.format("RequestTask[%s]", 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)}. */ private static final class CancelRequestTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Canceling this request. */ private final EReplyFeed.ERequest mRequest; /** * 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 feed the canceled request applies to this feed. * @param cb the method callback. */ private CancelRequestTask(final EReplyFeed.ERequest request, final EReplyFeed feed, final CancelRequestCallback cb) { super (feed); mRequest = request; 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. */ @Override public void run() { final Object target = (mFeed.eClient()).target(); if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest(this.toString()); } if (target != null) { try { mCallback.call(mRequest); } catch (Throwable tex) { final String reason = String.format( "EReplier.cancelRequest(%s) exception", mFeed); if (sLogger.isLoggable(Level.FINE)) { sLogger.log(Level.WARNING, reason, tex); } else { sLogger.log(Level.WARNING, reason); } } } return; } // 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 //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link EReplier#request(EReplyFeed.ERequest)} method name. */ /* package */ static final String REQUEST_METHOD = "request"; /** * {@link EReplier#cancelRequest(EReplyFeed.ERequest)} method * name. */ /* package */ static final String CANCEL_METHOD = "cancelRequest"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(EReplyFeed.class.getName()); //----------------------------------------------------------- // 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 RequestCallback mRequestCallback; /** * Contains the functional interface callback for request * messages. If not explicitly set by client, then defaults * to * {@link EReplier#cancelRequest(EReplyFeed.ERequest)}. */ private CancelRequestCallback mCancelCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new reply feed for the given client and request * subject. * @param client the application client. * @param scope whether this feed support local feeds, remote * feeds, or both. * @param condition the replier accepts requests only if it * passes this condition. * @param dataType the request message type. Used to * determine if the reply message is in the request message * {@link net.sf.eBus.messages.EReplyInfo} list. * @param subject this feed is associated with this request * subject. */ private EReplyFeed(final EClient client, final FeedScope scope, final ECondition condition, final MessageType dataType, final ERequestSubject subject) { super (client, scope, FeedType.REPLY_FEED, subject); mCondition = condition; mRequests = new ArrayList<>(); mDataType = dataType; mRequestCallback = null; mCancelCallback = null; } // end of EReplyFeed(...) // // 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"); } // Remove from the advertisers list if the client is // local and the feed supports remote clients. if (mEClient.isLocal() && (mScope == FeedScope.LOCAL_AND_REMOTE || mScope == FeedScope.REMOTE_ONLY)) { mAdvertisers.remove(this); } return; } // 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. // /** * 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. * @param cb the request callback. May be {@code null}. * @throws IllegalStateException * if this feed is either closed or advertised. */ @Override public void requestCallback(final RequestCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { throw ( new IllegalStateException( "advertisement in place")); } mRequestCallback = cb; return; } // 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)}. A * {@code null cb} means that cancellations are passed to the * {@link EReplier#cancelRequest(EReplyFeed.ERequest)} * override. * @param cb the cancel request callback. May be * {@code null}. * @throws IllegalStateException * if this feed is either closed or advertised. */ @Override public void cancelRequestCallback(final CancelRequestCallback cb) { if (!mIsActive.get()) { throw ( new IllegalStateException("feed is inactive")); } if (mInPlace) { throw ( new IllegalStateException( "advertisement in place")); } mCancelCallback = cb; return; } // end of cancelRequestCallback(CancelRequestCallback) /** * 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; if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s replier %d, feed %d: setting %s feed state to %s (%s).", mEClient.location(), mEClient.clientId(), mFeedId, key(), update, mScope)); } // Forward the update to the subject. ((ERequestSubject) mSubject).updateFeedState(this); } // No change. Nothing to do. return; } // end of updateFeedState(EFeedState) /** * Returns {@code true} if the replier is still active and * the request message passes the reply condition. If this * method returns {@code true}, then * {@link #request(ERequestFeed.ERequest, ERequestMessage)} * is used to pass the request to the replier. * @param msg test this request message against the reply * condition. * @param loc requestor location. * @return {@code true} if {@code msg} is acceptable. * * @see #open(EReplier, EMessageKey, EFeed.FeedScope, ECondition) * @see #request(ERequestFeed.ERequest, ERequestMessage) */ /* package */ boolean checkRequest(final ERequestMessage msg, final ClientLocation loc) { return (mIsActive.get() && mCondition.test(msg) && mScope.supports(loc)); } // end of checkRequest(ERequestMessage, ClientLocation) /** * Forwards the given request message and request object to * the client, returning a {@link ERequest} object. Note that * {@link #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}. * * @see #checkRequest(ERequestMessage, EClient.ClientLocation) */ /* 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, mCancelCallback); synchronized (mRequests) { mRequests.add(retval); } } return (retval); } // end of request(ERequest, ERequestMessage) // // end of Set Methods. //----------------------------------------------------------- /** * Returns a reply advertiser feed for the specified request * message class and subject. The calls {@link #advertise} * on the returned feed in order to start receiving the * specified request messages. * @param client the application object subscribing to the * request. May not be {@code null}. * @param key the request message class and subject. * May not be {@code null} and must reference a request * message class. * @param scope whether this feed supports only local * clients, remote clients, or both. * @param condition accept request messages only if the * messages passes this condition. May be {@code null}. If * {@code null}, then the * {@link #NO_CONDITION default condition} which accepts all * request messages is used. * @return the newly created reply feed. * @throws NullPointerException * if any of the required parameters is {@code null}. * @throws IllegalArgumentException * if any of the given parameters is invalid. * * @see #advertise() * @see EFeed#close() */ @SuppressWarnings ("unchecked") public static EReplyFeed open(final EReplier client, final EMessageKey key, final FeedScope scope, ECondition condition) { // Validate the parameters. // Are the parameters non-null references? Objects.requireNonNull(client, "client is null"); Objects.requireNonNull(key, "key is null"); Objects.requireNonNull(scope, "scope is null"); // Is the message key for a request? if (!key.isRequest()) { throw ( new IllegalArgumentException( String.format( "%s is not a request message", key))); } final MessageType dataType = (MessageType) DataType.findType(key.messageClass()); return (open(client, key, scope, condition, ClientLocation.LOCAL, dataType, false)); } // end of open(...) /** * Returns a reply advertiser feed for the specified request * message class and subject. The calls {@link #advertise} * on the returned feed in order to start receiving the * specified request messages. *

* This method does not parameter validation since this is * a {@code package private} method. *

* @param client the application object subscribing to the * request. May not be {@code null}. * @param key the request message class and subject. * May not be {@code null} and must reference a request * message class. * @param scope whether this feed supports only local * clients, remote clients, or both. * @param condition accept request messages only if the * messages passes this condition. May be {@code null}. If * {@code null}, then the * {@link #NO_CONDITION default condition} which accepts all * request messages is used. * @param l {@code client} location. * @param dataType the request message type. Used to * determine if the reply message is in the request message * {@link net.sf.eBus.messages.EReplyInfo} list. * @param isMulti {@code true} if this is part of a multiple * key feed. If {@code true}, this feed is not added to the * client feed list. * @return the newly created reply feed. */ public static EReplyFeed open(final EReplier client, final EMessageKey key, final FeedScope scope, final ECondition condition, final ClientLocation l, final MessageType dataType, final boolean isMulti) { // If a null condition was passed in, then use the // default NO_CONDITION. final ECondition replyCondition = (condition == null ? NO_CONDITION : condition); final EClient eClient = EClient.findOrCreateClient(client, l); final ERequestSubject subject = ERequestSubject.findOrCreate(key); final EReplyFeed retval = new EReplyFeed(eClient, scope, replyCondition, dataType, subject); // Let the client know it is being referenced by another // feed - but only if this is not part of a multiple key // feed. In that case, the multiple key feed is added to // the client. if (!isMulti) { eClient.addFeed(retval); } // Add to the advertiser list if the client is local and // the feed supports remote clients. if (eClient.isLocal() && (scope == FeedScope.LOCAL_AND_REMOTE || scope == FeedScope.REMOTE_ONLY)) { mAdvertisers.add(retval); } if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "%s replier %d, Feed %d: opened %s (%s).", l, eClient.clientId(), retval.feedId(), key, scope)); } return (retval); } // end of open(...) /** * 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) { // If the callbacks were not put in place, then use // the defaults. if (mRequestCallback == null) { // Did the client override the request method? if (!isOverridden(REQUEST_METHOD, EReplyFeed.ERequest.class)) { // That's a Bozo no-no. throw ( new IllegalStateException( REQUEST_METHOD + " not overridden and requestCallback not set")); } mRequestCallback = ((EReplier) mEClient.target())::request; } if (mCancelCallback == null) { // Did the client override the cancelRequest // method? if (!isOverridden(CANCEL_METHOD, EReplyFeed.ERequest.class)) { // That's a negative. throw ( new IllegalStateException( CANCEL_METHOD + " not overridden and cancelRequestCallback not set")); } mCancelCallback = ((EReplier) mEClient.target())::cancelRequest; } if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s replier %d, Feed %d: advertising %s (%s).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope)); } ((ERequestSubject) mSubject).advertise(this); mInPlace = true; } return; } // 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) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s replier %d, Feed %d: unadvertising (%s).", 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"); } return; } // 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)); return; } // 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); } return; } // 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(requests); mRequests.clear(); } requests.forEach( request -> { request.reply( new EReplyMessage( (request.key()).subject(), ReplyStatus.ERROR, reason)); }); return; } // end of failRequests(String) } // end of class EReplyFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy