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

net.sf.eBus.client.ERequestSubject 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 2014 - 2016. Charles W. Rapp
// All Rights Reserved.
//

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;
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 methods.
//

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

    /**
     * Creates a new request subject instance for the given
     * message key.
     * @param key the request message key.
     */
    @SuppressWarnings ({"unchecked", "rawtypes"})
    /* package */ 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,
                    new AdMessage(mKey,
                                  adStatus,
                                  MessageType.REPLY,
                                  mRemoteFeedState));
        }

        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);
        }

        return;
    } // 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);

        return;
    } // 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,
                    new AdMessage(
                        mKey,
                        AdMessage.AdStatus.ADD,
                        EMessage.MessageType.REQUEST,
                        mRemoteFeedState)));
        }

        return;
    } // 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,
                    new AdMessage(
                        mKey,
                        AdMessage.AdStatus.REMOVE,
                        EMessage.MessageType.REQUEST,
                        EFeedState.DOWN)));
        }

        return;
    } // 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,
                    new FeedStatusMessage(mRemoteFeedState)));
        }

        return;
    } // 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.
     * @param key request message key.
     * @return the request subject for the given message key.
     */
    @SuppressWarnings ("unchecked")
    /* package */ static ERequestSubject
        findOrCreate(final EMessageKey key)
    {
        ERequestSubject retval;

        synchronized (sSubjects)
        {
            final String keyString = key.keyString();

            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));
                }
            }
        }

        return (retval);
    } // end of findOrCreate(EMessageKey)

//---------------------------------------------------------------
// Member data.
//

    /**
     * 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;

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

    /**
     * The subject class logger.
     */
    private static final Logger sLogger =
        Logger.getLogger(ERequestSubject.class.getName());
} // end of class ERequestSubject




© 2015 - 2025 Weber Informatics LLC | Privacy Policy