
net.sf.eBus.client.ESubject 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, 2011, 2013-2016. Charles W. Rapp.
// All Rights Reserved.
//
package net.sf.eBus.client;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
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 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 list message key dictionary entries.
*/
/* package */ static List findKeys()
{
final Collection subjects;
final List retval = new LinkedList<>();
synchronized (sSubjects)
{
subjects = sSubjects.values();
}
// Put the subject message keys into the returned list.
subjects.forEach(subject -> retval.add(subject.key()));
return (retval);
} // 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 List retval = new LinkedList<>();
synchronized (sSubjects)
{
subjects = sSubjects.values(query);
}
// Put the subject message keys into the returned list.
subjects.forEach(subject -> retval.add(subject.key()));
return (retval);
} // 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 List retval = new LinkedList<>();
// For each subject, get its ad message.
for (ESubject subject : sSubjects.values())
{
msg = subject.localAd(adStatus);
if (msg != null)
{
retval.add(msg);
}
}
return (retval);
} // end of localAds(AdStatus)
//
// end of Get Methods.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Set Methods.
//
/**
* 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)
{
final String keyString = key.keyString();
synchronized (sSubjects)
{
if (!sSubjects.containsKey(keyString))
{
// Is this a notification message?
if (key.isNotification())
{
// Yes. Then create a notify subject.
sSubjects.put(
keyString, new ENotifySubject(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, new ERequestSubject(key));
}
}
}
return;
} // 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 ->
{
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,
new ENotifySubject(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,
new ERequestSubject(key));
}
}
});
}
return;
} // 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);
return;
} // 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);
return;
} // 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;
EMessageKey key;
String keyString;
synchronized (sSubjects)
{
for (i = 0; i < numKeys; ++i)
{
try
{
key = (EMessageKey) ois.readObject();
keyString = key.keyString();
// Make sure the message key is not already
// defined.
if (!sSubjects.containsKey(keyString))
{
// Keys are either notification or
// request.
if (key.isNotification())
{
sSubjects.put(
keyString,
new ENotifySubject(key));
}
else
{
sSubjects.put(
keyString,
new ERequestSubject(key));
}
}
}
catch (ClassNotFoundException classex)
{
throw (
new IOException(
"ois contains a non-EMessageKey object",
classex));
}
}
}
return;
} // end of loadKeys(ObjectInputStream)
/**
* 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());
}
return;
} // end of storeKeys(Collection<>)
//---------------------------------------------------------------
// Member data.
//
/**
* The subject message key.
*/
protected final EMessageKey mKey;
//-----------------------------------------------------------
// Statics.
//
/**
* Maps the {@link EMessageKey} to its eBus subject.
* {@link EMessageKey#keyString} is used for the mapping.
*/
protected static final TernarySearchTree sSubjects =
new TernarySearchTree<>();
//---------------------------------------------------------------
// Inner classes.
//
/**
* Adds the subject information to the periodic status
* report for all extant subjects.
*/
private static final class SubjectStatusReporter
implements StatusReporter
{
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates the singleton eBus subject status reporter.
*/
public SubjectStatusReporter()
{}
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// 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);
return;
} // end of reportStatus(PrintWriter)
//
// end of StatusReporter Interface Implementation.
//-------------------------------------------------------
//-----------------------------------------------------------
// Member data.
//
} // end of class SubjectStatusReporter
} // end of class ESubject