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

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

The newest version!
//
// Copyright 2014 - 2016, 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.List;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.sysmessages.AdMessage;
import net.sf.eBus.client.sysmessages.AdMessage.AdStatus;
import net.sf.eBus.client.sysmessages.FeedStatusMessage;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.messages.EMessage.MessageType;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.ERequestMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@code ERequestSubject} connects repliers with requesters
 * based on their respective feed scopes. A request is
 * satisfied if there is at least one replier within its scope
 * whose {@link ECondition condition} accepts the
 * {@link ERequestMessage request message}. An
 * {@link ERequestFeed.ERequest} is created for each matching
 * replier and forwarded to
 * {@link EReplier#request(net.sf.eBus.client.EReplyFeed.ERequest)}.
 * The replier
 * {@link EReplyFeed.ERequest#reply(net.sf.eBus.messages.EReplyMessage) replies}
 * via the {@code ERequest} instance rather than via
 * {@link EReplyFeed}.
 *
 * @author Charles Rapp
 */

/* package */ final class ERequestSubject
    extends ESubject
{
//---------------------------------------------------------------
// Member data.
//

    //-----------------------------------------------------------
    // Statics.
    //

    /**
     * The subject class logger.
     */
    private static final Logger sLogger =
        LoggerFactory.getLogger(ERequestSubject.class);

    //-----------------------------------------------------------
    // Locals.
    //

    /**
     * Currently registered request feeds.
     */
    private final EFeedList mRequestors;

    /**
     * The currently advertised repliers, one list per feed location.
     */
    private final EFeedList mRepliers;

    /**
     * Tracks the reply feed state for remote clients. This is
     * necessary since we must send a feed status message to
     * remote clients when the feed state transitions between
     * UP and DOWN.
     */
    private EFeedState mRemoteFeedState;

//---------------------------------------------------------------
// Member methods.
//

    //-----------------------------------------------------------
    // Constructors.
    //

    /**
     * Creates a new request subject instance for the given
     * message key.
     * @param key the request message key.
     */
    @SuppressWarnings ({"unchecked", "rawtypes"})
    private ERequestSubject(final EMessageKey key)
    {
        super (key);

        mRequestors = new EFeedList<>();
        mRepliers = new EFeedList<>();
        mRemoteFeedState = EFeedState.DOWN;
    } // end of ERequestSubject(EMessageKey)

    //
    // end of Constructors.
    //-----------------------------------------------------------

    //-----------------------------------------------------------
    // ESubject Abstract Method Implementations.
    //

    @Override
    /* package */ EMessageHeader localAd(final AdStatus adStatus)
    {
        final EFeedState fs =
            mRepliers.feedState(ClientLocation.REMOTE);
        EMessageHeader retval = null;

        if (fs != EFeedState.UNKNOWN)
        {
            retval =
                new EMessageHeader(
                    (SystemMessageType.AD).keyId(),
                    ERemoteApp.NO_ID,
                    ERemoteApp.NO_ID,
                    (AdMessage.builder()).messageKey(mKey)
                                         .adStatus(adStatus)
                                         .adMessageType(MessageType.REPLY)
                                         .feedState(mRemoteFeedState)
                                         .build());
        }

        return (retval);
    } // end of localAd(AdStatus)

    //
    // end of ESubject Abstract Method Implementations.
    //-----------------------------------------------------------

    /**
     * Adds a local request feed to the request feed list. This
     * feed is local by definition since request feeds are not
     * forwarded to remote eBus applications.
     * @param feed add this request feed.
     */
    /* package */ void subscribe(final ERequestFeed feed)
    {
        sLogger.debug("{}: adding {}/{} requestor {}, feed {}.",
                      mKey,
                      feed.location(),
                      feed.scope(),
                      feed.clientId(),
                      feed.feedId());

        mRequestors.add(feed);

        // Iterate over the extant repliers, adding
        // repliers *if* they match the requestor's feed and
        // scope.
        final List feeds =
            mRepliers.feeds(feed.scope());

        feeds.forEach(r -> feed.addReplier(r.location(), r));
    } // end of subscribe(ERequestFeed)

    /**
     * Removes a local request feed from the request feed list.
     * This feed is local by definition since request feeds are
     * not forwarded to remote eBus applications.
     * @param feed remote this request feed.
     */
    /* package */ void unsubscribe(final ERequestFeed feed)
    {
        sLogger.debug("{}: removing {}/{} requestor {}, feed {}.",
                      mKey,
                      feed.location(),
                      feed.scope(),
                      feed.clientId(),
                      feed.feedId());

        mRequestors.remove(feed);
    } // end of unsubscribe(ERequestFeed)

    /**
     * Adds the given reply feed to the replier feed list. If
     * this is the first local client/local & remote feed,
     * then the advertisement is forwarded to all remote eBus
     * applications.
     * @param feed a replier feed.
     */
    /* package */ void advertise(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();
        final EFeedState remoteFeedState;

        sLogger.debug("{}: adding {}/{} replier {}, feed {}.",
                      mKey,
                      location,
                      scope,
                      feed.clientId(),
                      feed.feedId());

        remoteFeedState = mRepliers.add(feed);

        // Forward this advertisement to all request feeds.
        // Note: all request feeds are local since they are not
        // advertised to remote eBus applications and, therefore,
        // match all reply feeds *except* local repliers with a
        // remote only scope.
        if (location == ClientLocation.REMOTE ||
            scope != FeedScope.REMOTE_ONLY)
        {
            final List feeds =
                mRequestors.feeds(FeedScope.LOCAL_AND_REMOTE);

            feeds.forEach(f -> f.addReplier(location, feed));
        }

        // If this feed is local client/local&remote feed or
        // remote only and the first one to boot, then forward
        // the advertisement to all remote eBus applications
        // currently connected.
        if (location == ClientLocation.LOCAL &&
            (scope == FeedScope.LOCAL_AND_REMOTE ||
             scope == FeedScope.REMOTE_ONLY) &&
            remoteFeedState != EFeedState.UNKNOWN)
        {
            // Yes. Forward the ad to all remote
            // applications.
            sLogger.debug(
                "{}: forward ad to remote applications.", mKey);

            ERemoteApp.forwardAll(
                new EMessageHeader(
                    (SystemMessageType.AD).keyId(),
                    ERemoteApp.NO_ID,
                    ERemoteApp.NO_ID,
                    (AdMessage.builder()).messageKey(mKey)
                                         .adStatus(AdStatus.ADD)
                                         .adMessageType(MessageType.REQUEST)
                                         .feedState(mRemoteFeedState)
                                         .build()));
        }
    } // end of subscribe(EReplyFeed)

    /**
     * Removes the given replier feed from the replier feed list.
     * If this is the last local client/local & remote
     * feed, then the advertisement is retracted from all remote
     * eBus applications.
     * @param feed a replier feed.
     */
    /* package */ void unadvertise(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();
        final int feedCount = mRepliers.remove(feed);

        sLogger.debug("{}: removing {}/{} replier {}, feed {}.",
                      mKey,
                      location,
                      scope,
                      feed.clientId(),
                      feed.feedId());

        // Forward this unadvertisement to all request feeds.
        // Note: all request feeds are local since they are not
        // advertised to remote eBus applications and, therefore,
        // match all reply feeds *except* local repliers with a
        // remote only scope.
        if ((location == ClientLocation.LOCAL &&
             scope != FeedScope.REMOTE_ONLY) ||
            location == ClientLocation.REMOTE)
        {
            final List feeds =
                mRequestors.feeds(FeedScope.LOCAL_AND_REMOTE);

            feeds.forEach(r -> r.removeReplier(feed));
        }

        // If this feed is local client/local&remote feed and
        // the last one to boot, then forward the unadvertisement
        // to all remote eBus applications currently connected.
        // Is this the last local feed to support remote
        // requests?
        if (location == ClientLocation.LOCAL &&
            (scope == FeedScope.LOCAL_AND_REMOTE ||
             scope == FeedScope.REMOTE_ONLY) &&
            feedCount == 0)
        {
            // Yes. Retract the ad to all remote
            // applications.
            sLogger.debug(
                "{}: forward ad retraction to remote applications (feed count={})",
                mKey,
                feedCount);

            ERemoteApp.forwardAll(
                new EMessageHeader(
                    (SystemMessageType.AD).keyId(),
                    ERemoteApp.NO_ID,
                    ERemoteApp.NO_ID,
                    (AdMessage.builder()).messageKey(mKey)
                                         .adStatus(AdStatus.REMOVE)
                                         .adMessageType(MessageType.REQUEST)
                                         .feedState(EFeedState.DOWN)
                                         .build()));
        }
    } // end of unsubscribe(EReplyFeed)

    /**
     * Updates the replier feed state as contained in
     * {@code feed}. If this feed state change results in
     * requestor feed state change, then all interested
     * requestors are informed of this change.
     * @param feed update this feed's state.
     */
    /* package */ void updateFeedState(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();
        final EFeedState oldState = mRemoteFeedState;

        sLogger.debug(
            "{}: updating {} replier {} {} to {} (scope: {}).",
            mKey,
            feed.location(),
            feed.clientId(),
            feed.key(),
            feed.feedState(),
            scope);

        mRequestors.updateCount(feed, feed.feedState());

        // If this update is from a local client and the subject
        // feed *from a remote perspective* transitions between
        // up and down due to this update, then forward this
        // feed state to all remote eBus applications currently
        // connected.
        if (location == ClientLocation.LOCAL &&
            (scope == FeedScope.LOCAL_AND_REMOTE ||
             scope == FeedScope.REMOTE_ONLY) &&
            (mRemoteFeedState =
                 mRepliers.feedState(
                     ClientLocation.REMOTE)) != oldState)
        {
            // Yes. Forward feed state to remote applications.
            sLogger.debug(
                "{}: forward {} feed state to remote applications.",
                mKey,
                mRemoteFeedState);

            ERemoteApp.forwardAll(
                new EMessageHeader(
                    (SystemMessageType.FEED_STATUS).keyId(),
                    ERemoteApp.NO_ID,
                    ERemoteApp.NO_ID,
                    (FeedStatusMessage.builder()).feedState(mRemoteFeedState)
                                                 .build()));
        }
    } // end of updateFeedState(EReplyFeed)

    /**
     * Returns the request subject for the given message key. If
     * the subject does not already exist, then creates the
     * request subject for {@code key} and stores it in the
     * subject map.
     * 

* The caller is expected to verify that {@code key} is a * non-{@code null} reference to a request message. This * method does not validate {@code key}. *

* @param key request message key. * @return the request subject for the given message key. */ @SuppressWarnings ("unchecked") /* package */ static ERequestSubject findOrCreate(final EMessageKey key) { final ERequestSubject retval; synchronized (sSubjects) { retval = doFindOrCreate(key); } return (retval); } // end of findOrCreate(EMessageKey) /* package */ static ERequestSubject doFindOrCreate(final EMessageKey key) { final String keyString = key.keyString(); ERequestSubject retval; retval = (ERequestSubject) sSubjects.get(keyString); // Does this subject already exist? if (retval == null) { // No. So create it now. retval = new ERequestSubject(key); sSubjects.put(keyString, retval); sLogger.trace("{}: created request subject.", key); // Let listeners know about this new request subject. ESubject.forwardUpdate(SubjectType.REQUEST, key); } return (retval); } // end of doFindOrCreate(EMessageKey) } // end of class ERequestSubject




© 2015 - 2024 Weber Informatics LLC | Privacy Policy