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

net.sf.eBus.client.ENotifySubject 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 (C) 2010 - 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.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.ENotificationMessage;

/**
 * {@code ENotifySubject} connects notification publishers with
 * subscribers, based on their respective feed scopes.
 * A notification feed is up if there is at least one subscriber
 * for a specified notification message key. After a publisher
 * advertises, {@code ENotifySubject} informs the publisher if
 * there are any subscribers in its feed scope or not by
 * calling
 * {@link EPublisher#publishStatus(EFeedState, EPublishFeed)}
 * with the appropriate {@link EFeedState feed state}. The
 * publisher should not
 * {@link EPublishFeed#publish(ENotificationMessage) publish}
 * notification messages when the feed is
 * {@link EFeedState#DOWN down} as this results in an
 * {@link IllegalStateException}. The subscriber is notified if
 * there are any publishers within its subscription feed scope
 * via the
 * {@link ESubscriber#feedStatus(EFeedState, ESubscribeFeed)}
 * callback.
 * 

* Notice that it is possible for publishers and subscribers for * the same message key to have different feed states because the * feeds have a different scope. For example, an application has * two subscribers to the message key * {@code com.acme.mgt.SystemStatus:ActiveServer}. One subscriber * is a local only scope and the other has remote only scope. * Since the application is not running on the active server but * the stand-by, the first subscription feed will be down while * the second feed state will be up (assuming the stand-by server * is connected to the active server). *

* * @see ESubject * @see EPublisher * @see ESubscriber * @see ENotifyFeed * @see EPublishFeed * @see ESubscribeFeed * * @author Charles Rapp */ /* package */ final class ENotifySubject extends ESubject { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Statics. // /** * The notification subject class logger. */ private static final Logger sLogger = Logger.getLogger(ENotifySubject.class.getName()); //----------------------------------------------------------- // Locals. // /** * The currently advertised publish feeds. There is a * separate list for each feed zone. */ private final EFeedList mAdvertisers; /** * The currently subscribed subscribe feeds. There is a * separate list for each feed zone. */ private final EFeedList mSubscribers; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new notification subject for the given unique * message key. * @param key the unique notification message key. */ @SuppressWarnings ({"unchecked", "rawtypes"}) /* package */ ENotifySubject(final EMessageKey key) { super (key); mAdvertisers = new EFeedList<>(); mSubscribers = new EFeedList<>(); } // end of ENotifySubject(EMessageKey) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // ESubject Abstract Method Implementations. // @Override /* package */ EMessageHeader localAd(final AdStatus adStatus) { EMessageHeader retval = null; if (mAdvertisers.supports(ClientLocation.REMOTE) > 0) { retval = new EMessageHeader( (SystemMessageType.AD).keyId(), ERemoteApp.NO_ID, ERemoteApp.NO_ID, new AdMessage( mKey, adStatus, MessageType.NOTIFICATION, mAdvertisers.feedState( ClientLocation.REMOTE))); } return (retval); } // end of localAd(AdStatus) // // end of ESubject Abstract Method Implementations. //----------------------------------------------------------- /** * Adds the given publisher feed to the advertiser feed list * for the feed's scope. *

* If this is the first local client/local & remote feed, * then the advertisement is forwarded to all remote eBus * applications. *

* @param feed a publishing feed. * @return returns the current subscriber feed state. */ /* package */ synchronized EFeedState advertise(final EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); final int activationCount = mSubscribers.isSupportedBy(scope); if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s: adding %s client/%s scope advertiser %d, feed %d.", mKey, location, scope, feed.clientId(), feed.feedId())); } // Add the feed to the advertisers feed list. mAdvertisers.add(feed); // DO NOT INFORM SUBSCRIBERS ABOUT THIS ADVERTISEMENT. // Wait for the publish state update and then inform // subscribers. We do not know yet if the publisher feed // is up. // If this feed is local client and a remote feed 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) && mAdvertisers.supports(FeedScope.REMOTE_ONLY) == 1) { ERemoteApp.forwardAll( new EMessageHeader( (SystemMessageType.AD).keyId(), ERemoteApp.NO_ID, ERemoteApp.NO_ID, new AdMessage(mKey, AdMessage.AdStatus.ADD, MessageType.NOTIFICATION, mAdvertisers.feedState( ClientLocation.REMOTE)))); } // Post the current subscriber activation count to the // publisher, returning the feed state. return (feed.updateActivate(activationCount)); } // end of advertise(EPublishFeed) /** * Removes the given publisher feed to the advertiser 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 publishing feed. */ /* package */ synchronized void unadvertise(EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); final int feedCount = mAdvertisers.remove(feed); if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s: removing %s client/%s scope advertiser %d, feed %d.", mKey, location, scope, feed.clientId(), feed.feedId())); } // If the feed's publish state is up, then set the feed // state to down. if (feed.feedState() == EFeedState.UP) { mSubscribers.updateCount(feed, EFeedState.DOWN); } // If this feed is local client, remote feed and // the last one to boot, then forward the unadvertisement // to all remote eBus applications currently connected. if (location == ClientLocation.LOCAL && (scope == FeedScope.LOCAL_AND_REMOTE || scope == FeedScope.REMOTE_ONLY) && feedCount == 0) { ERemoteApp.forwardAll( new EMessageHeader( (SystemMessageType.AD).keyId(), ERemoteApp.NO_ID, ERemoteApp.NO_ID, new AdMessage(mKey, AdMessage.AdStatus.REMOVE, MessageType.NOTIFICATION, EFeedState.DOWN))); } return; } // end of unadvertise(EPublishFeed) /** * Updates the publisher state as contained in * {@code feed}. If this publish state change results in * subscriber feed state change, then all interested * subscribers are informed of this change. * @param feed update this feed's state. */ /* package */ synchronized void updateFeedState(final EPublishFeed feed) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s: updating %s publisher %d %s to %s.", mKey, feed.location(), feed.clientId(), feed.key(), feed.publishState())); } mSubscribers.updateCount(feed, feed.publishState()); return; } // end of updateFeedState(EPublishFeed) /** * Forwards the message to all subscriber feeds matching the * publisher feed zone. * @param msg post this message to the subscriber feeds. * @param feed the publisher feed. */ /* package */ synchronized void publish(final ENotificationMessage msg, final EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); final Iterator fit = mSubscribers.iterator(scope); if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format("%s: %s/%s publisher %d, feed %d message:%n%s", mKey, location, scope, feed.clientId(), feed.feedId(), feed.feedState(), msg)); } else if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format("%s: %s/%s publisher %d, feed %d message.", mKey, location, scope, feed.clientId(), feed.feedId(), feed.feedState())); } // Post the message to each contra-zone subscribers. while (fit.hasNext()) { (fit.next()).notify(msg); } return; } // end of publish(ENotificiationMessage, EPublishFeed) /** * Adds the given subscriber feed to the subscriber feed * list. Informs publisher feeds if this subscription changes * their publisher feed state. * @param f subscriber feed. */ /* package */ synchronized void subscribe(ESubscribeFeed f) { final ClientLocation location = f.location(); final FeedScope scope = f.scope(); if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s: adding %s/%s subscriber %d, feed %d.", mKey, location, scope, f.clientId(), f.feedId())); } // Add the feed to the subscribers feed list. mSubscribers.add(f); // Update the publisher activation count. f.updateActivate( mAdvertisers.updateCount(f, EFeedState.UP)); return; } // end of subscribe(ESubscribeFeed) /** * Removes the given subscriber feed from the subscriber feed * list. Informs publisher feeds if this retraction changes * their publisher feed state. * @param feed subscriber feed. */ /* package */ synchronized void unsubscribe(final ESubscribeFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format("%s: removing %s/%s subscriber %d, feed %d.", mKey, location, scope, feed.clientId(), feed.feedId())); } // Remove the feed from the subscribes feed list. mSubscribers.remove(feed); // Update the publish activation count. mAdvertisers.updateCount(feed, EFeedState.DOWN); return; } // end of unsubscribe(ESubscribeFeed) /** * Returns the notification subject for the given message * key. If this subject does not already exist, then creates * the subject. *

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

* @param key notification message key. * @return the notification subject for the given message * key. * @throws IllegalArgumentException * if {@code key} is either {@code null} or not a * notification message key. */ @SuppressWarnings ("unchecked") /* package */ static ENotifySubject findOrCreate(final EMessageKey key) { ENotifySubject retval; synchronized (sSubjects) { final String keyString = key.keyString(); retval = (ENotifySubject) sSubjects.get(keyString); // Do we need to open the subject? if (retval == null) { // Yes. Do so and store it away in the subjects // tree. retval = new ENotifySubject(key); sSubjects.put(keyString, retval); if (sLogger.isLoggable(Level.FINE)) { sLogger.finest( String.format( "%s: created notification subject.", key)); } } } return (retval); } // end of findOrCreate(EMessageKey) } // end of class ENotifySubject




© 2015 - 2025 Weber Informatics LLC | Privacy Policy