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

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

//
// Copyright 2010, 2011, 2013-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 com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.sysmessages.AdMessage;
import net.sf.eBus.client.sysmessages.AdMessage.AdStatus;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.util.TernarySearchTree;
import net.sf.eBus.util.logging.StatusReporter;
import net.sf.eBus.util.regex.Pattern;

/**
 * The eBus subject performs the actual work of connecting
 * application instances together via their respective
 * {@link EFeed feeds}. {@code ESubject} instances are
 * responsible for connecting {@link EPublisher publishers} to
 * {@link ESubscriber subscribers} and {@link EReplier repliers}
 * with {@link ERequestor requestors}. There is one subject
 * instance for each unique {@link EMessageKey message key}.
 * 

* This class is responsible for maintaining a static ternary * search tree mapping a * {@link EMessageKey#keyString() key string} to the eBus subject * instance for that message key. *

*

* The eBus API is intended to be extended with new feed and * subject classes. These extensions would provide more * sophisticated notification and request/reply types. One * example is a notification feed that combines historical and * live updates or just historical, depending on what the * subscriber requests. This allows a subscriber to join the feed * at any time, not missing any previously posted notifications. *

* * @see ENotifySubject * @see ERequestSubject * @see EFeed * * @author Charles Rapp */ @SuppressWarnings ("unchecked") /* package */ abstract class ESubject implements Comparable { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Statics. // /** * Maps the {@link EMessageKey} to its eBus subject. * {@link EMessageKey#keyString} is used for the mapping. */ protected static final TernarySearchTree sSubjects = new TernarySearchTree<>(); /** * Forward newly added subject's type and key to these * listeners. */ private static final List sSubjectListeners = new ArrayList<>(); /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(ESubject.class.getName()); //----------------------------------------------------------- // Locals. // /** * The subject message key. */ protected final EMessageKey mKey; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new subject instance for the given message key. * @param key the unique subject message key. */ protected ESubject(final EMessageKey key) { mKey = key; } // end of ESubject(EMessageKey) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Returns a non-{@code null} {@link AdMessage} if this * subject currently has a local feed which supports a remote * client. Note that the feed state does not need to be * {@link EFeedState#UP}, only advertised. If no such feed * exists, then returns {@code null}. * @param adStatus either add or remote this advertisement. * @return either an {@code AdMessage} or {@code null}. */ /* package */ abstract EMessageHeader localAd(final AdStatus adStatus); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // Comparable Interface Implementation. // /** * Returns an integer value <, equal to or > zero * depending on whether {@code this ESubject} * instance is <, equal to or > {@code subject}. * The comparison is based on * {@link net.sf.eBus.messages.EMessageKey}. * @param subject comparison instance. * @return an integer value <, equal to or > zero * depending on whether {@code this ESubject} * instance is <, equal to or > {@code subject}. */ @Override public int compareTo(final ESubject subject) { return (mKey.compareTo(subject.mKey)); } // end of compareTo(ESubject) // // end of Comparable Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Object Method Overrides. // /** * Returns {@code true} if {@code o} is a * non-{@code null ESubject} whose message key equals * {@code this} subject; otherwise returns {@code false}. * @param o comparison object. * @return {@code true} if {@code o} message keys equals * {@code this} message key. */ @Override public boolean equals(final Object o) { boolean retcode = (this == o); if (!retcode && o instanceof ESubject) { retcode = mKey.equals(((ESubject) o).mKey); } return (retcode); } // end of equals(Object) /** * Returns the message key hash code. * @return the hash code for this subject instance. */ @Override public int hashCode() { return (mKey.hashCode()); } // end of hashCode() /** * Returns the message key text representation. * @return the message key text representation. */ @Override public String toString() { return (mKey.toString()); } // end of toString() // // end of Object Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns this subject message key. * @return this subject message key. */ public final EMessageKey key() { return (mKey); } // end of key() /** * Returns a non-{@code null}, possibly empty, message key * list taken from the current message key dictionary * entries. * @return mutable list message key dictionary entries. */ /* package */ static List findKeys() { final Collection subjects; final ImmutableList.Builder retval = ImmutableList.builder(); synchronized (sSubjects) { subjects = sSubjects.values(); } // Put the subject message keys into the returned list. subjects.forEach(subject -> retval.add(subject.key())); return (retval.build()); } // end of findKeys() /** * Returns a non-{@code null}, possibly empty, list of * message keys from the message key dictionary matching the * given regular express query. * @param query match against this pattern. * @return list of message key dictionary entries matching * {@code query}. */ /* package */ static List findKeys(final Pattern query) { final Collection subjects; final ImmutableList.Builder retval = ImmutableList.builder(); synchronized (sSubjects) { subjects = sSubjects.values(query); } // Put the subject message keys into the returned list. subjects.forEach(subject -> retval.add(subject.key())); return (retval.build()); } // end of findKeys(Pattern) /** * Returns a list of local advertisements which * support a remote client. If subject returns a * non-{@code null} message and {@code adStatus} is * {@link AdStatus#ADD}, then put the subject's feed state * message on to the list as well. * @param adStatus either add or remove this advertisement. * @return advertise and feed state message list. */ /* package */ static List localAds(final AdStatus adStatus) { EMessageHeader msg; final ImmutableList.Builder retval = ImmutableList.builder(); // For each subject, get its ad message. for (ESubject subject : sSubjects.values()) { msg = subject.localAd(adStatus); if (msg != null) { retval.add(msg); } } return (retval.build()); } // end of localAds(AdStatus) // // end of Get Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Adds the given subject listener to the listeners list. * Does nothing if the listener is already registered. * @param l add this subject listener. */ /* package */ static void addListener(final ISubjectListener l) { final EClient eClient = EClient.findOrCreateClient(l, ClientLocation.LOCAL); synchronized (sSubjectListeners) { if (!sSubjectListeners.contains(eClient)) { sSubjectListeners.add(eClient); } } } // end of addListener(ISubjectListener) /** * Removes subject listener from the listeners list. * @param l remove this subject listener. */ /* package */ static void removeListener(final ISubjectListener l) { removeListener(EClient.findClient(l)); } // end of removeListener(ISubjectListener) /** * Remove subject listener client from the listeners list. * @param eClient subject listener. */ /* package */ static void removeListener(final EClient eClient) { if (eClient != null) { synchronized (sSubjectListeners) { sSubjectListeners.remove(eClient); } } } // end of removeListener(eClient) /** * Adds the given message key to the subject tree if not * already in the tree. *

* Argument validated prior to this call. *

* @param key add this message key to subject tree. */ /* package */ static void addSubject(final EMessageKey key) { synchronized (sSubjects) { doAddSubject(key); } } // end of addSubject(EMessageKey) /** * Adds all the given message keys to the message key tree * but only if the key is not already in the tree. *

* Argument validated prior to this call. *

* @param keys add these message keys to subject tree. */ /* package */ static void addAllSubjects(final Collection keys) { synchronized (sSubjects) { keys.forEach(key -> doAddSubject(key)); } } // end of addAllSubjects(Collection<>) // // end of Set Methods. //----------------------------------------------------------- /** * Writes the entire message key dictionary to the given * object output stream. The caller is responsible for * opening and closing the object output stream. *

* Only message keys are written to the object output stream. * The associated eBus subjects and their related feeds are * not stored. When the message key is re-loaded from the * object stream at application start, the eBus subjects are * recreated but not the feeds. Feeds must be re-opened by * the application upon start. *

* @param oos load message keys to this object output stream. * @throws IOException * if an error occurs writing message keys to {@code oos}. * * @see #storeKeys(Pattern, ObjectOutputStream) * @see #loadKeys(ObjectInputStream) */ /* package */ static void storeKeys(final ObjectOutputStream oos) throws IOException { final Collection keys; synchronized (sSubjects) { keys = sSubjects.values(); } storeKeys(keys, oos); } // end of storeKeys(ObjectOutputStream) /** * Writes those message keys matching the given query to * the object output stream. * @param query message key regular expression pattern. * @param oos object output stream. * @throws IOException * if an error occurs writing message keys to {@code oos}. */ /* package */static void storeKeys(final Pattern query, final ObjectOutputStream oos) throws IOException { final Collection keys; synchronized (sSubjects) { keys = sSubjects.values(query); } storeKeys(keys, oos); } // end of storeKeys(Pattern, ObjectOutputStream) /** * Loads the message keys extracted from the object input * stream back into the eBus message key dictionary. Note: * existing message keys are not overwritten or replaced by * the keys found in {@code ois}. * @param ois read in message keys from this object input * stream. * @throws IOException * if an error occurs reading in message keys. */ /* package */ static void loadKeys(final ObjectInputStream ois) throws IOException { final int numKeys = ois.readInt(); int i; synchronized (sSubjects) { for (i = 0; i < numKeys; ++i) { try { doAddSubject((EMessageKey) ois.readObject()); } catch (ClassNotFoundException classex) { throw ( new IOException( "ois contains a non-EMessageKey object", classex)); } } } } // end of loadKeys(ObjectInputStream) /** * Post the specified new subject update to all registered * listeners. * @param type new subject's type (notification or request). * @param key new subject's message key. */ protected static void forwardUpdate(final SubjectType type, final EMessageKey key) { synchronized (sSubjectListeners) { sSubjectListeners.forEach( l -> l.dispatch(new SubjectTask(l, type, key))); } } // end of forwardUpdate(SubjectType, EMessageKey) /** * Performs the actual work of writing message keys to the * object output stream. * @param subjects write subject keys to {@code oos}. * @param oos output message keys to this object output * stream. * @throws IOException * if an error occurs writing the message keys to the object * output stream. */ private static void storeKeys(final Collection subjects, final ObjectOutputStream oos) throws IOException { // Store the number of message key entries first. oos.writeInt(subjects.size()); for (ESubject subject : subjects) { // Now write out each message key. oos.writeObject(subject.key()); } } // end of storeKeys(Collection<>) /** * Performs the actual work of adding the given message key * to the subjects map. * @param key add this message key to the map if not already * in the map. * * @see #addSubject(EMessageKey) * @see #addAllSubjects(Collection) * @see #loadKeys(ObjectInputStream) */ private static void doAddSubject(final EMessageKey key) { final String keyString = key.keyString(); if (!sSubjects.containsKey(keyString)) { // Is this a notification message? if (key.isNotification()) { // Yes. Then create a notify subject. sSubjects.put( keyString, ENotifySubject.doFindOrCreate(key)); } // Otherwise this a request message. The caller // has already determined that key is either a // notification or request. else { // Yes. Then create a request subject. sSubjects.put( keyString, ERequestSubject.doFindOrCreate(key)); } } } // end of doAddSubject(EMessageKey) //--------------------------------------------------------------- // Inner classes. // /** * Forwards subject update to eBus client implementing * {@link ISubjectListener} interface. */ private static final class SubjectTask implements Runnable { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // private final EClient mEClient; private final SubjectType mType; private final EMessageKey mKey; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private SubjectTask(final EClient eClient, final SubjectType type, final EMessageKey key) { mEClient = eClient; mType = type; mKey = key; } // end of SubjectTask(EClient,SubjectType,EMessageKey) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Runnable Interface Implementation. // @Override public void run() { final Object target = mEClient.target(); // Is the target listener still alive? if (target != null) { try { final ISubjectListener l = (ISubjectListener) target; l.subjectUpdate(mType, mKey); } catch (Exception jex) { sLogger.log( Level.WARNING, String.format( "SubjectTask[%s, %s, %s] exception", (target.getClass()).getName(), mType, mKey), jex); } } } // end of run() // // end of Runnable Interface Implementation. //------------------------------------------------------- } // end of class SubjectTask /** * Adds the subject information to the periodic status * report for all extant subjects. */ private static final class SubjectStatusReporter implements StatusReporter { //----------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // StatusReporter Interface Implementation. // /** * Appends the client and subject count to the periodic * status report. * @param report periodic status report. */ @Override public void reportStatus(final PrintWriter report) { final int clientCount = EClient.clientCount(); final int subjectCount = sSubjects.size(); report.println(); report.println("EClient:"); report.format(" clients: %,d%n", clientCount); report.println(); report.println("eBus Subject:"); report.format(" subjects: %,d%n", subjectCount); } // end of reportStatus(PrintWriter) // // end of StatusReporter Interface Implementation. //------------------------------------------------------- } // end of class SubjectStatusReporter } // end of class ESubject




© 2015 - 2025 Weber Informatics LLC | Privacy Policy