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

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

The newest version!
//
// Copyright 2010 - 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 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@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 = LoggerFactory.getLogger(ENotifySubject.class); //----------------------------------------------------------- // Locals. // /** * The currently advertised publish feeds. There is a * separate list for each feed zone. */ private final EFeedList mPublishers; /** * 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. */ /* package */ ENotifySubject(final EMessageKey key) { super (key); mPublishers = new EFeedList<>(); mSubscribers = new EFeedList<>(); } // end of ENotifySubject(EMessageKey) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // ESubject Abstract Method Implementations. // /** * Returns a message header containing a local publisher * advertisement if any of the local publishers * support remote publication. Otherwise return {@code null}. * @param adStatus adding or removing advertisements. * @return advertisement message or {@code null} if this * notification feed cannot be remotely advertised. */ @Override /* package */ EMessageHeader localAd(final AdStatus adStatus) { final EFeedState fs = mPublishers.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.NOTIFICATION) .feedState(fs) .build()); } 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 */ EFeedState advertise(final EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); final int activationCount = mSubscribers.isSupportedBy(scope); final EFeedState fs; sLogger.debug( "{}: adding {} client/{} scope advertiser {}, feed {}.", mKey, location, scope, feed.clientId(), feed.feedId()); // Add the feed to the advertisers feed list. fs = mPublishers.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. // Multicast feeds are *not* forwarded to remote eBus // applications. if (location == ClientLocation.LOCAL && scope.intersects(FeedScope.REMOTE_ONLY) && fs != EFeedState.UNKNOWN) { ERemoteApp.forwardAll( new EMessageHeader( (SystemMessageType.AD).keyId(), ERemoteApp.NO_ID, ERemoteApp.NO_ID, (AdMessage.builder()).messageKey(mKey) .adStatus(AdStatus.ADD) .adMessageType(MessageType.NOTIFICATION) .feedState(fs) .build())); } // 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 */ void unadvertise(EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); final int feedCount = mPublishers.remove(feed); sLogger.debug( "{}: removing {} client/{} scope advertiser {}, feed {}.", 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, (AdMessage.builder()).messageKey(mKey) .adStatus(AdStatus.REMOVE) .adMessageType(MessageType.NOTIFICATION) .feedState(EFeedState.DOWN) .build())); } } // 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 */ void updateFeedState(final EPublishFeed feed) { sLogger.debug("{}: updating {} publisher {} {} to {}.", mKey, feed.location(), feed.clientId(), feed.key(), feed.publishState()); mSubscribers.updateCount(feed, feed.publishState()); } // 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 */ void publish(final ENotificationMessage msg, final EPublishFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); if (sLogger.isTraceEnabled()) { sLogger.trace( "{}: {}/{} publisher {}, feed {} message:\n{}", mKey, location, scope, feed.clientId(), feed.feedId(), feed.feedState(), msg); } else { sLogger.debug( "{}: {}/{} publisher {}, feed {} message.", mKey, location, scope, feed.clientId(), feed.feedId(), feed.feedState()); } // Post the message to each contra-zone subscribers. (mSubscribers.feeds(scope)).forEach(f -> f.notify(msg)); exhaust(msg); } // 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 */ void subscribe(final ESubscribeFeed f) { sLogger.debug("{}: adding {}/{} subscriber {}, feed {}.", mKey, f.location(), f.scope(), f.clientId(), f.feedId()); // Add the feed to the subscribers feed list. mSubscribers.add(f); // Update the publisher activation count. f.updateActivate( mPublishers.updateCount(f, EFeedState.UP)); } // 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 */ void unsubscribe(final ESubscribeFeed feed) { final ClientLocation location = feed.location(); final FeedScope scope = feed.scope(); sLogger.debug( "{}: removing {}/{} subscriber {}, feed {}.", mKey, location, scope, feed.clientId(), feed.feedId()); // Remove the feed from the subscribes feed list. mSubscribers.remove(feed); // Update the publish activation count. mPublishers.updateCount(feed, EFeedState.DOWN); } // 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) { final ENotifySubject retval; synchronized (sSubjects) { retval = doFindOrCreate(key); } return (retval); } // end of findOrCreate(EMessageKey) /** * Performs the actual work of finding or creating a * notification message subject. Caller is expected to * {@code syncronized (sSubjects)} prior to calling this * method. * @param key find or create this notification subject. * @return notification subject. */ /* package */ static ENotifySubject doFindOrCreate(final EMessageKey key) { final String keyString = key.keyString(); ENotifySubject 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); sLogger.trace( "{}: created notification subject.", key); // Let listeners know about this new notification // subject. ESubject.forwardUpdate( SubjectType.NOTIFICATION, key); } return (retval); } // end of doFindOrCreate(EMessageKey) } // end of class ENotifySubject




© 2015 - 2024 Weber Informatics LLC | Privacy Policy