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

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

The newest version!
//
// Copyright 2024 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 javax.annotation.Nullable;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.logging.AsyncLoggerFactory;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.ERequestMessage;
import net.sf.eBus.util.Validator;
import org.slf4j.Logger;

/**
 * {@code ERequestMonitorFeed} is the application entry point
 * for monitoring {@link ERequestMessage request messages} to and
 * {@link EReplyMessage reply messages} from local
 * {@link EReplier repliers}. This feed cannot be used to monitor
 * messages between remote requesters and repliers. Follow these
 * steps to use this feed when monitoring requests and replies:
 * 

* Step 1: Implement * {@link ERequestMonitor} interface. *

*

* Step 2: * {@link ERequestMonitorFeed.Builder Build} request monitor * for {@code ERequestMonitor} instance and * {@link net.sf.eBus.messages.EMessageKey type+topic request message key}. * Note that the feed scope is set to {@code FeedScope.LOCAL} and * cannot be overridden. *

*

* Use * {@link Builder#requestCallback(MessageCallback)}, * {@link Builder#replyCallback(MessageCallback)}, and * {@link Builder#cancelCallback(MessageCallback)} to set Java * lambda expressions used in place of {@link ERequestMonitor} * interface methods. *

*

* Step 3: * {@link #monitor() Start monitoring} requests to and replies * from local repliers. Please note that unlike other eBus roles * (publisher, subscriber, etc.) the request monitor is * not informed when the request feed is up or down. * That is because the monitor is not part of this conversation * but is standing to the side watching the message flow. *

*

* Step 4: * {@code ERequestMonitor} receives request messages, reply * messages, and cancellations flowing between requesters and * local repliers. *

*

* Step 5: When * request monitor is shutting down, * {@link ERequestMonitorFeed#unmonitor() stop} monitoring and * {@link ERequestMonitorFeed#close() close} the monitor feed. *

*

Example use of ERequestMonitorFeed

*
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import net.sf.eBus.client.ERequestMonitor;
import net.sf.eBus.client.ERequestMonitorFeed;
import net.sf.eBus.client.EScheduledExecutor;
import net.sf.eBus.client.IETimer;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.ERequestMessage;

Step 1: Implement ERequestMonitor interface.
public class CatalogRequestMonitor implements ERequestMonitor {
    private static final Duration REPLY_DELAY = Duration.ofHours(3L);

    private final EMessageKey mKey;
    private final Map<String, RequestInfo> mOrders;
    private ERequestMonitorFeed mFeed;
    private IETimer mReplyTimer;

    public CatalogRequestMonitor(final String subject) {
        mKey = new EMessageKey(com.acme.CatalogOrder.class, subject);
        mOrders = new HashMap<>();
    }

    @Override public void startup() {
        try
        {
            final EScheduledExecutor executor = EScheduledExecutor.getExecutor("blocking");

            Step 2: Open a request monitor feed for request message key. May override ERequestMonitor interfaces methods.
            mFeed = (ERequestMonitor.builder()).target(this)
                                               .messageKey(mKey)
                                               .build();

            Step 3: Start monitoring.
            mFeed.monitor();

            mReplyTimer = executor.scheduleAtFixedRate(this::onReplyTimeout, this, REPLY_DELAY, REPLY_DELAY);
        } catch (IllegalArgumentException argex) {
            // Monitoring failed. Place recovery code here.
        }
    }

    Step 4: Wait for requests, replies, and cancellations to arrive.
    @Override public void request(final ERequestMessage request, final String replier) {
        final CatalogOrder order = (CatalogOrder) request;

        // No need to synchronize orders map since all callbacks are on this object's dispatcher.
        mOrders.put(order.orderId, new RequestInfo(order, replier));
    }

    @Override public void reply(final EReplyMessage reply, final String replier) {
        final CatalogOrderReply orderReply = (CatalogOrderReply) reply;
        final String orderId = orderReply.orderId;

        // Is this the final reply?
        if (orderReply.isFinal) {
            // Yes, stop monitoring the order.
            mOrders.remove(orderId);
        } else {
            // No, update order timestamp since replier is actively working this order.
            (mOrders.get(orderId)).updateTimestamp();
        }
    }

    @Override public void cancel(final ERequestMessage request, final String replier) {
        final CatalogOrder order = (CatalogOrder) request;

        // Remove this order since it is now defunct.
        mOrders.remove(order.orderId);
    }

    @Override public void shutdown() {
        mOrders.clear();

        Step 5: When shutting down, either unmonitor or close request monitor feed.
        if (mFeed != null) {
            mFeed.close();
            mFeed = null;
        }
    }

    private void onReplyTimeout() {
        // Iterate over orders looking for failed replies.
        for (OrderInfo oInfo : mOrders.values()) {
            // If timed out waiting for a reply, raise an alarm.
        }
    }
}
*

* Please note: there currently is no matching * multi-request monitor feed. *

* * @see ERequestMonitor * * @author Charles W. Rapp */ public final class ERequestMonitorFeed extends ESingleFeed implements IERequestMonitorFeed { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * {@link ERequestMonitor#request(ERequestMessage, EReplier)} * method name is {@value}. */ private static final String REQUEST_METHOD = "request"; /** * {@link ERequestMonitor#reply(EReplyMessage, EReplier)} * method name is {@value}. */ private static final String REPLY_METHOD = "reply"; /** * {@link ERequestMonitor#cancel(ERequestMessage, int)} * method name is {@value}. */ private static final String CANCEL_METHOD = "cancel"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = AsyncLoggerFactory.getLogger(); //----------------------------------------------------------- // Locals. // /** * Deliver request messages to this callback. */ private final MessageCallback mRequestCallback; /** * Delivery reply messages to this callback. */ private final MessageCallback mReplyCallback; /** * Deliver cancel request messages to this callback. */ private final MessageCallback mCancelCallback; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new request monitor instance instance based on * builder configuration. Note that builder configuration is * guaranteed to be valid prior to calling this constructor. * @param builder contains valid request monitor feed * configuration. */ private ERequestMonitorFeed(final Builder builder) { super (builder); mRequestCallback = builder.mRequestCallback; mReplyCallback = builder.mReplyCallback; mCancelCallback = builder.mCancelCallback; } // end of ERequestMonitorFeed(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // IERequestMonitorFeed Interface Implementation. // /** * Always returns zero because request monitors do not 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, FeedState) /** * If request monitor is in place, then retract this monitor * feed. * * @see EFeed#close() */ @Override protected void inactivate() { if (mInPlace) { ((ERequestSubject) mSubject).unmonitor(this); mInPlace = false; } } // end of inactivate() /** * Adds this request monitor feed to associated request * subject. If this monitor feed is currently in place, then * does nothing. * @throws IllegalStateException * if this feed is closed. * * @see #unmonitor() * @see EFeed#close() */ @Override public void monitor() { // Is this feed still active? if (!mIsActive.get()) { // No, cannot start monitoring on an inactive feed. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Feed is still active. Is monitoring already in place? if (!mInPlace) { // No, start monitoring now. sLogger.debug( "{} request monitor, feed {}: monitoring {} ({}).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope); ((ERequestSubject) mSubject).monitor(this); mInPlace = true; } } // end of monitor() /** * Retracts this request monitor feed from associated request * subject. Does nothing if this feed is not currently in * place. * @throws IllegalStateException * * @see #monitor() * @see EFeed#close() */ @Override public void unmonitor() { // Is this feed still active? if (!mIsActive.get()) { // No, cannot stop monitoring on an inactive feed. throw (new IllegalStateException(FEED_IS_INACTIVE)); } // Feed is still active. Is monitoring in place? if (mInPlace) { // Yes, stop monitoring now. sLogger.debug( "{} request monitor, feed {}: monitoring {} ({}).", mEClient.location(), mEClient.clientId(), mFeedId, mSubject.key(), mScope); ((ERequestSubject) mSubject).unmonitor(this); mInPlace = false; } } // end of unmonitor() // // end of IERequestMonitorFeed Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if this request monitor feed is both * open and monitoring; otherwise returns {@code false}. * @return {@code true} if this request monitor feed is both * open and monitoring. */ public boolean isMonitoring() { return (mIsActive.get() && mInPlace); } // end of isMonitoring() // // end of Get Methods. //----------------------------------------------------------- /** * Returns a new {@code ERequestMonitorFeed} builder * instance. This instance is used to build a single request * monitor feed instance and should not be used to create * multiple such feeds. * @return new request monitor feed builder. */ public static Builder builder() { return (new ERequestMonitorFeed.Builder()); } // end of builder() /** * Forwards request message and replier pair to this feed's * request monitor. * @param request inbound request message. * @param replier target replier's eBus object name. */ /* package */ void dispatch(final ERequestMessage request, final String replier) { mEClient.dispatch(new MonitorTask<>(request, replier, mRequestCallback, this)); } // end of dispatch(ERequestMessage, String) /** * Forwards reply message and replier pair to this feed's * request monitor. * @param reply outbound reply message. * @param replier source replier's eBus object name. */ /* package */ void dispatch(final EReplyMessage reply, final String replier) { mEClient.dispatch(new MonitorTask<>(reply, replier, mReplyCallback, this)); } // end of dispatch(EReplyMessage, String) /** * Forwards request cancellation and replier pair to this * feed's request monitor. * @param request canceled request message. * @param replier target replier's eBus object name. */ /* package */ void dispatchCancel(final ERequestMessage request, final String replier) { mEClient.dispatch(new MonitorTask<>(request, replier, mCancelCallback, this)); } // end of dispatchCancel(ERequestMessage, String) //--------------------------------------------------------------- // Inner classes. // /** * {@code ERequestMonitorFeed.Builder} is used to create an * {@code ERequestMonitorFeed} instance. A {@code Builder} * instance is acquired from * {@link ERequestMonitorFeed#builder()}. The following * example shows how to create a {@code ERequestMonitorFeed} * instance using a {@code Builder}. The example also shows * how {@code ERequestMonitor} interface methods may be * replaced with lambda expressions. *
@Override public void startup() {
    final EMessageKey = new EMessageKey(com.acme.CatalogOrder.class, subject);
    final ERequestMonitorFeed feed (ERequestMonitorFeed.builder()).target(this)
                                                                  .messageKey(key)
                                                                  .requestCallback(this::onRequest)
                                                                  .replyCallback(this::onReply)
                                                                  .cancelCallback(this::onCancel)
                                                                  .build();
    ...
}

private void onRequest(final ERequestMessage request, final String replier) {
    ...
}

private void onReply(final EReplyMessage reply, final String replier) {
    ...
}

private void onCancel(final ERequestMessage request, final String replier) {
    ...
}
* * @see #builder() */ public static final class Builder extends ESingleFeed.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Deliver request messages to this callback. */ private MessageCallback mRequestCallback; /** * Delivery reply messages to this callback. */ private MessageCallback mReplyCallback; /** * Inform request monitor that a given request is * canceled. */ private MessageCallback mCancelCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new {@code ERequestMonitorFeed} builder * instance. Note that request monitor feeds have a * local feed scope which cannot be changed. */ private Builder() { super (FeedType.REQUEST_MONITOR_FEED, ERequestMonitorFeed.class); mScope = FeedScope.LOCAL_ONLY; mLocation = ClientLocation.LOCAL; } // 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() /** * Verifies that message key is for a request subject and * that both request and reply callbacks are set. * @param problems place invalid configuration settings * in this problems list. * @return {@code problems} to allow for method chaining. */ @Override protected Validator validate(Validator problems) { // If the callbacks were not put in place, then use // the defaults. if (mRequestCallback == null && mTarget != null && isOverridden(REQUEST_METHOD, ERequestMessage.class, String.class)) { mRequestCallback = ((ERequestMonitor) mTarget)::request; } if (mReplyCallback == null && mTarget != null && isOverridden(REPLY_METHOD, EReplyMessage.class, String.class)) { mReplyCallback = ((ERequestMonitor) mTarget)::reply; } if (mCancelCallback == null && mTarget != null && isOverridden(CANCEL_METHOD, ERequestMessage.class, String.class)) { mCancelCallback = ((ERequestMonitor) mTarget)::cancel; } return (super.validate(problems) .requireTrue((mKey != null && mKey.isRequest()), "messageKey", "messageKey is not a request") .requireNotNull(mRequestCallback, "requestCallback") .requireNotNull(mReplyCallback, "replyCallback") .requireNotNull(mCancelCallback, "cancelCallback")); } // end of validate(Validator) /** * Returns a new {@code ERequestMonitorFeed} instance * based on {@code this Builder}'s configuration. * @return new request monitor feed. */ @Override protected ERequestMonitorFeed buildImpl() { return (new ERequestMonitorFeed(this)); } // end of buildImpl() /** * Returns {@code this Builder} reference. * @return {@code this Builder} reference. */ @Override protected Builder self() { return (this); } // end of self() // // end of Abstract Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Throws {@code UnsupportedOperationException} because * request monitor feeds have local-only scope which may * not be overridden. * @param scope feed scope. * @return {@code this Builder} instance. * @throws UnsupportedOperationException * default local-only feed scope may not be overridden. */ @Override public Builder scope(final FeedScope scope) { throw ( new UnsupportedOperationException( "local-only feed scope may not be overridden")); } // end of scope(FeedScope) /** * Puts inbound request callback in place. If {@code cb} * is not {@code null}, requests will be passed to * {@code cb} rather than * {@link ERequestMonitor#request(ERequestMessage, String)}. * If {@code cb} is {@code null}, then request messages * are passed to the * {@link ERequestMonitor#request(ERequestMessage, String)} * override. * @param cb request callback. May be {@code null}. * @return {@code this Builder} instance. */ public Builder requestCallback(@Nullable final MessageCallback cb) { mRequestCallback = cb; return (this); } // end of requestCallback(MessageCallback<>) /** * Puts outbound reply callback in place. If {@code cb} * is not {@code null}, replies will be passed to * {@code cb} rather than * {@link ERequestMonitor#reply(EReplyMessage, String)}. * If {@code cb} is {@code null}, then reply messages * are passed to the * {@link ERequestMonitor#reply(EReplyMessage, String)} * override. * @param cb reply callback. May be {@code null}. * @return {@code this Builder} instance. */ public Builder replyCallback(@Nullable final MessageCallback cb) { mReplyCallback = cb; return (this); } // end of replyCallback(MessageCallback<>) /** * Puts inbound request cancellation callback in place. * if {@code cb} is not {@code null}, cancellations will * be passed to {@code cb} rather than * {@link ERequestMonitor#cancel(ERequestMessage, String)}. * If {@code cb} is {@code null}, then request messages * are passed to the * {@link ERequestMonitor#cancel(ERequestMessage, String)} * override. * @param cb cancellation callback. May be {@code null}. * @return {@code this Builder} instance. */ public Builder cancelCallback(@Nullable final MessageCallback cb) { mCancelCallback = cb; return (this); } // end of cancelCallback(() // // end of Set Methods. //------------------------------------------------------- } // end of class Builder /** * Task used to for eBus request and reply messages to * a request monitor instance. */ private static final class MonitorTask extends AbstractClientTask { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Forward this message to request monitor. */ private final M mMessage; /** * Request or reply message is either to or from eBus * replier with this eBus object name. */ private final String mReplier; /** * Forward message to request monitor using this * callback. */ private final MessageCallback mCallback; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private MonitorTask(final M message, final String replier, final MessageCallback cb, final ERequestMonitorFeed feed) { super (feed); mMessage = message; mReplier = replier; mCallback = cb; } // end of AbstractMonitorTask(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementations. // /** * Forwards message to target object using configured * callback lambda expression. */ @Override public void run() { final Object target = (mFeed.eClient()).target(); sLogger.trace("{}", this); // Is the request monitor still around? if (target != null) { // Yes, forward message. try { mCallback.call(mMessage, mReplier); } catch (Throwable tex) { final String reason = "MonitorTask[{} {}]"; final String className = (target.getClass()).getName(); if (sLogger.isDebugEnabled()) { sLogger.warn(reason, className, mMessage.key(), tex); } else { sLogger.warn(reason, className, mMessage.key()); } } } // No, target went bye-bye. } // end of run() // // end of Runnable Interface Implementations. //------------------------------------------------------- //------------------------------------------------------- // Object Method Overrides. // @Override public String toString() { return (String.format("MonitorTask[%s, %s]", mMessage.key(), mReplier)); } // end of toString() // // end of Object Method Overrides. //------------------------------------------------------- } // end of class AbstractMonitorTask } // end of class ERequestMonitorFeed




© 2015 - 2025 Weber Informatics LLC | Privacy Policy