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

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

//
// 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.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
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;

/**
 * {@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 =
        Logger.getLogger(ERequestSubject.class.getName());

    //-----------------------------------------------------------
    // 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)
    {
        EMessageHeader retval = null;

        if (mRepliers.supports(ClientLocation.REMOTE) > 0)
        {
            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 */
    synchronized void subscribe(
        final ERequestFeed feed)
    {
        if (sLogger.isLoggable(Level.FINER))
        {
            sLogger.finer(
                String.format("%s: adding %s/%s requestor %d, feed %d.",
                    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 Iterator rit =
            mRepliers.iterator(feed.scope());
        EReplyFeed replier;

        while (rit.hasNext())
        {
            replier = rit.next();
            feed.addReplier(replier.location(), replier);
        }
    } // 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 */ synchronized void
        unsubscribe(final ERequestFeed feed)
    {
        if (sLogger.isLoggable(Level.FINER))
        {
            sLogger.finer(
                String.format("%s: removing %s/%s requestor %d, feed %d.",
                    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 */ synchronized void
        advertise(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();

        if (sLogger.isLoggable(Level.FINER))
        {
            sLogger.finer(
                String.format("%s: adding %s/%s replier %d, feed %d.",
                    mKey,
                    location,
                    scope,
                    feed.clientId(),
                    feed.feedId()));
        }

        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 Iterator qit =
                mRequestors.iterator(FeedScope.LOCAL_AND_REMOTE);

            while (qit.hasNext())
            {
                (qit.next()).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) &&
            mRepliers.supports(FeedScope.REMOTE_ONLY) == 1)
        {
            // Yes. Forward the ad to all remote
            // applications.
            if (sLogger.isLoggable(Level.FINE))
            {
                sLogger.fine(
                    String.format("%s: 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 */ synchronized void
        unadvertise(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();
        final int feedCount = mRepliers.remove(feed);

        if (sLogger.isLoggable(Level.FINER))
        {
            sLogger.finer(
                String.format("%s: removing %s/%s replier %d, feed %d.",
                    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)
        {
            final Iterator qit =
                mRequestors.iterator(FeedScope.LOCAL_AND_REMOTE);

            while (qit.hasNext())
            {
                (qit.next()).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.
            if (sLogger.isLoggable(Level.FINE))
            {
                sLogger.fine(
                    String.format("%s: forward ad retraction to remote applications (feed count=%d)",
                        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 */ synchronized void
        updateFeedState(final EReplyFeed feed)
    {
        final ClientLocation location = feed.location();
        final FeedScope scope = feed.scope();
        final EFeedState oldState = mRemoteFeedState;

        if (sLogger.isLoggable(Level.FINER))
        {
            sLogger.finer(
                String.format("%s: updating %s replier %d %s to %s (scope: %s).",
                    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.
            if (sLogger.isLoggable(Level.FINE))
            {
                sLogger.fine(
                    String.format("%s: forward %s 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); if (sLogger.isLoggable(Level.FINE)) { sLogger.finest( String.format( "%s: 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 - 2025 Weber Informatics LLC | Privacy Policy