public abstract class EFeed extends java.lang.Object implements IEFeed
EPublisher
for
EPublishFeed
), opens a feed instance, and then
activates the feed. The feed can be activated, deactivated
multiple times. Once the feed is closed
,
the feed instance cannot be used again. A new feed instance
must be opened again.
An application defines a feed's scope
when
opening the feed. This scope defines the feed's visibility.
That feed may be visible only within the local JVM, within
both local and remote JVMs, and in remote JVMs only. For
example, if a subscription feed
has
local-only scope, then it will receive notifications from
local publisher feeds
only. Notifications
from remote publishers will not be forwarded to the local-only
subscriber. The following table shows the interface between
feed scopes:
Local Only | Local & Remote | Remote Only | |
---|---|---|---|
Local Only | Match | Match | No match |
Local & Remote | Match | Match | Match |
Remote Only | No match | Match | No match |
(Notice that a remote feed may only support a local & remote feed and not other remote only feeds.)
Feed scope gives the application developer control over how "far" a message will go. If a notification message is intended to stay within a JVM, both the publish and subscribe feeds may be set to a local only scope. If a notification is meant for remote access only, the the publish feed is set to remote only scope. These examples also apply to request/reply feeds.
eBus calls an application instance back from only one thread
at any given time. While that thread may change over time,
only one eBus thread will callback the application instance
at any given time. The eBus API is thread-safe and an
application may call eBus methods from multiple, simultaneous
threads. The benefit of this model is that if an application
instance is accessed only by eBus only and not by any other
non-eBus thread, then the application instance is effectively
single-threaded. Such an application instance does not need to
use synchronized
keyword or locks, simplifying the
application class implementation.
A feed maintains a weak reference
to the
application instance. If the application is finalized while
still owning open feeds, those feeds will be automatically
closed when eBus detects this finalization.
Each feed is assigned a identifier that is unique within the application instance scope and feed lifespan. Once a feed is closed, its identifier is recycled. Put in another way: an application instance's active feeds all have unique integer identifiers. These identifiers are not unique across different application instances. These identifiers are not unique across an application instance lifespan if that instance opens and closes feeds multiple times. It is likely that newly opened feed will have the same identifier as a previously closed feed.
The eBus API is intended to be extended with new feed subclasses. 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.
eBus v. 4.5.0 added the ability to directly add messages keys
to the eBus message key dictionary and retrieve keys from said
dictionary. Prior to this version, message keys were
indirectly added to the dictionary when opening feeds. This
feature added to support the new multikey feeds
EMultiPublishFeed
, EMultiSubscribeFeed
,
and EMultiReplyFeed
. Multi-subject feeds may use a
query
to match a
variable number of keys. This is why
addKey(EMessageKey)
,
addAllKeys(java.util.Collection)
and
loadKeys(ObjectInputStream)
methods are provided:
unless the message key dictionary is populated with keys prior
to creating a multi-subject query feed, the query would find
no matching keys.
Multi-subject feeds act as proxies between the application
client and the individual EPublishFeed
,
ESubscribeFeed
, ERequestFeed
and
EReplyFeed
in the multi-subject feed. The
multi-subject feed opens, advertises/subscribes, and closes
all subordinate feeds in unison. The individual feeds all
reference the same client and client callback methods. If a
multi-subject feed is for 100 subjects, then the client
receives callbacks from all 100 subordinate feed and
not for the single multi-subject feed.
Note: a multi-subject feed is not
EFeed
subclass. However, multi-subject feed behavior
is the same as an EFeed
and may be treated by the
application as if it were an EFeed
.
ESubject
,
ENotifyFeed
,
EPublishFeed
,
ESubscribeFeed
,
ERequestFeed
,
EReplyFeed
,
EMultiPublishFeed
,
EMultiSubscribeFeed
,
EMultiRequestFeed
,
EMultiReplyFeed
Modifier and Type | Class and Description |
---|---|
protected static class |
EFeed.AbstractClientTask
Base class for eBus client callback tasks created by
feeds.
|
protected static class |
EFeed.Builder<F extends EFeed,T extends EObject,B extends EFeed.Builder<F,T,?>>
Base class for all
EFeed builders. |
static class |
EFeed.FeedScope
Feed scope is either restricted to this JVM, to both this
JVM and remote JVMs, and to remote JVMs only.
|
protected class |
EFeed.NotifyTask
This task forwards a notification message to
ESubscriber.notify(ENotificationMessage, IESubscribeFeed) . |
protected class |
EFeed.StatusTask<T extends IEFeed>
Used to issue a feed status callback.
|
Modifier and Type | Field and Description |
---|---|
static java.lang.String |
FEED_IS_INACTIVE
Message associated with attempting to use an inactive
message feed.
|
static java.lang.String |
FEED_NOT_ADVERTISED
Message associated with attempting to use an unadvertised
message feed.
|
protected EClient |
mEClient
The client owning this feed.
|
protected int |
mFeedId
Immutable identifier unique within the client.
|
protected EFeedState |
mFeedState
|
protected boolean |
mInPlace
Set to
true when this feed is connected to its
subject. |
protected java.util.concurrent.atomic.AtomicBoolean |
mIsActive
Returns
true if this feed is active, meaning that
it can still be used by the client. |
protected EFeed.FeedScope |
mScope
The feed scope is either limited to this local JVM only,
remote JVMs only, and both local and remote JVMs.
|
static ECondition |
NO_CONDITION
The default condition always returns true.
|
protected static java.lang.String |
NOTIFY_METHOD
|
static java.lang.String |
NULL_CLIENT
Message associated with using a
null EClient . |
Modifier | Constructor and Description |
---|---|
protected |
EFeed(EClient client,
EFeed.FeedScope feedScope)
Creates an eBus feed for the given client subject, scope,
and feed type.
|
protected |
EFeed(EFeed.Builder<?,?,?> builder)
Creates an eBus feed based on the given builder
configuration.
|
Modifier and Type | Method and Description |
---|---|
static void |
addAllKeys(java.util.Collection<EMessageKey> keys)
Adds the given message key collection to the message key
dictionary if all keys are not null and reference
notification and/or request messages.
|
static void |
addKey(EMessageKey key)
Adds
key to the eBus message key dictionary if
key is not already defined. |
static void |
addListener(ISubjectListener l)
Adds the given subject listener to the listeners list.
|
protected static void |
checkScopes(EMessageKey key,
EFeed.FeedScope scope)
Checks if the message scope and feed scope are in
agreement.
|
int |
clientId()
Returns the feed client identifier.
|
void |
close()
Closes this feed, marking it as inactive.
|
static void |
createDispatcher(EConfigure.Dispatcher dispatcher)
Creates a new client dispatcher run queue based on the
given configuration.
|
static java.lang.String |
defaultDispatcher()
Returns the eBus default dispatcher's name.
|
EClient |
eClient()
Returns the eBus client referenced by this feed.
|
boolean |
equals(java.lang.Object o)
Returns
true if o is a
non-null EFeed instance with equal client and
feed identifiers. |
int |
feedId()
Returns the unique feed identifier.
|
EFeedState |
feedState()
Returns the current feed state.
|
static java.util.List<EMessageKey> |
findKeys()
Returns a non-
null , possibly empty, message key
list taken from the current message key dictionary
entries. |
static java.util.List<EMessageKey> |
findKeys(java.lang.Class<? extends EMessage> mc)
Returns a list containing message keys for the given
message class found in the message key dictionary.
|
static java.util.List<EMessageKey> |
findKeys(java.lang.Class<? extends EMessage> mc,
Pattern query)
Returns a list containing message keys for the given
message class and subject pattern found in the message key
dictionary.
|
int |
hashCode()
Returns the hash of the client and feed identifiers.
|
protected abstract void |
inactivate()
Closes the feed.
|
boolean |
inPlace()
Returns
true if this feed is "in place"
(subscribed or advertised) and false otherwise. |
boolean |
isActive()
Returns
true if this feed is still active and
false if inactive. |
boolean |
isFeedUp()
|
protected boolean |
isOverridden(java.lang.String methodName,
java.lang.Class<?>... params)
Returns
true if the application object stored in
EClient defines a method with the given name and
parameters. |
static void |
loadKeys(java.io.ObjectInputStream ois)
Reads the
message keys contained in
the given object input stream and loads them back into the
eBus message key dictionary. |
EClient.ClientLocation |
location()
|
static void |
register(EObject client)
Registers the application object
client with
eBus, assigning the client to the dispatcher configured
for the client's class, and using the defined
EObject.startup() and EObject.shutdown()
methods. |
static void |
register(EObject client,
java.lang.String dispatcherName)
Registers the application object
client with
eBus, assigning client to the named dispatcher. |
static void |
register(EObject client,
java.lang.String dispatcherName,
java.lang.Runnable startCb,
java.lang.Runnable shutdownCb)
Registers the application object
client with
eBus, assigning client to the named dispatcher. |
static void |
removeListener(ISubjectListener l)
Removes subject listener from the listeners list.
|
EFeed.FeedScope |
scope()
Returns the feed scope: local only, local & remote, or
remote only.
|
static void |
setExhaust(IMessageExhaust exhaust)
Sets the message exhaust to the given instance.
|
static void |
shutdown(EObject client)
Calls the shutdown method
registered
with eBus if-and-only-if the application object is
currently started. |
static void |
shutdown(java.util.List<? extends EObject> clients)
Call the registered shutdown method for each of the
application objects in
clients if-and-only-if
the application object is currently started. |
static void |
shutdownAll()
Calls the shutdown method for all currently registered
application objects
which are currently started. This method
is generally called by an application just prior to
shutting down.
|
static void |
startup(EObject client)
Calls the start-up method
registered
with eBus if-and-only-if the application object is not
currently started. |
static void |
startup(java.util.List<? extends EObject> clients)
Call the registered start-up method for each of the
application objects in
clients if-and-only-if
the application object is not currently started. |
static void |
startupAll()
Calls the start-up method for all currently registered
application objects
which are not currently started. This method
is generally called from an application's
static main method after having created and
registered the application's eBus objects. |
static void |
storeKeys(java.lang.Class<? extends EMessage> mc,
java.io.ObjectOutputStream oos)
Write those message keys associated with the given message
class to the object output stream.
|
static void |
storeKeys(java.lang.Class<? extends EMessage> mc,
Pattern query,
java.io.ObjectOutputStream oos)
Write those message keys associated with the given message
class and a subject matching the regular expression to the
object output stream.
|
static void |
storeKeys(java.io.ObjectOutputStream oos)
Writes the entire message key dictionary to the given
object output stream.
|
protected static int |
subclassDistance(java.lang.Class<?> subclass,
java.lang.Class<?> bc)
Returns the distance between the given subclass and base
class.
|
java.lang.String |
toString()
Returns a containing the feed message key and data member
values.
|
public static final ECondition NO_CONDITION
null
condition,
removing the need for a if
condition in message
routing code, which improves JIT performance.public static final java.lang.String FEED_IS_INACTIVE
public static final java.lang.String FEED_NOT_ADVERTISED
public static final java.lang.String NULL_CLIENT
null EClient
.protected static final java.lang.String NOTIFY_METHOD
protected final EClient mEClient
EClient
maintains a
weak reference to the application instance.protected final EFeed.FeedScope mScope
protected final java.util.concurrent.atomic.AtomicBoolean mIsActive
true
if this feed is active, meaning that
it can still be used by the client. Returns false
if this feed is inactive and cannot be used by the client
again. Once a feed is made inactive, it cannot become
active again.protected boolean mInPlace
true
when this feed is connected to its
subject. Initialized to false
.protected final int mFeedId
EClient
instances may have the same mFeedId
.protected EFeedState mFeedState
protected EFeed(EClient client, EFeed.FeedScope feedScope)
client
's
run queue
.client
- post eBus tasks to this client.feedScope
- this feed supports either local, local
& remote, or just remote feeds.protected EFeed(EFeed.Builder<?,?,?> builder)
builder
- contains feed configuration.protected abstract void inactivate()
public final int feedId()
public final EFeed.FeedScope scope()
public final EClient eClient()
public final boolean isActive()
true
if this feed is still active and
false
if inactive. Clients may only use active
feeds. Once a feed is closed, it is marked inactive and
may not be used by the client again.
Once a feed is closed, the unique feed identifier may be reused by a newly opened feed.
public final boolean inPlace()
true
if this feed is "in place"
(subscribed or advertised) and false
otherwise.public boolean isFeedUp()
public final EFeedState feedState()
public void close()
If this feed is already inactive, then does nothing.
public boolean equals(java.lang.Object o)
true
if o
is a
non-null EFeed
instance with equal client and
feed identifiers. Otherwise returns false
.equals
in class java.lang.Object
o
- comparison object.true
if o
is a
non-null EFeed
instance with equal client and
feed identifiers.public int hashCode()
hashCode
in class java.lang.Object
public java.lang.String toString()
toString
in class java.lang.Object
public final int clientId()
public final EClient.ClientLocation location()
public static java.lang.String defaultDispatcher()
registering
a client
to the default dispatcher.public static java.util.List<EMessageKey> findKeys()
null
, possibly empty, message key
list taken from the current message key dictionary
entries.public static java.util.List<EMessageKey> findKeys(java.lang.Class<? extends EMessage> mc)
mc
- message class.null
and possibly empty message key
list.public static java.util.List<EMessageKey> findKeys(java.lang.Class<? extends EMessage> mc, Pattern query)
mc
- message class.query
- message subject regular expression query.null
and possibly empty message key
list.public static void addListener(ISubjectListener l)
l
- add this subject listener.public static void removeListener(ISubjectListener l)
l
- remove this subject listener.public static void addKey(EMessageKey key)
key
to the eBus message key dictionary if
key
is not already defined. key
must
reference either a notification or request message since
only those message keys are stored in the message key
dictionary.key
- add this message key to the subjectjava.lang.IllegalArgumentException
- if key
is null
or does not reference
either a ENotificationMessage
or ERequestMessage
.public static void addAllKeys(java.util.Collection<EMessageKey> keys)
keys
contains a null
or non-notification/
request key, then none of the keys is placed into the
message key dictionary.
The listed keys must reference either a notification or request message since only those message keys are stored in the message key dictionary. The list may consist of a mixture of notification and request message keys.
keys
- put these notification and/or request message
keys into the message key dictionary.java.lang.IllegalArgumentException
- if keys
contains a null
or does not
reference either a
ENotificationMessage
or ERequestMessage
.public static void setExhaust(IMessageExhaust exhaust)
exhaust
via a
dispatch thread. Caller is responsible for opening and
closing the exhaust's underlying persistent store
appropriately.
Note: only one exhaust may be
configured at a time. When this method is called, the
current exhaust is replace with exhaust
. If an
application requires messages to be persisted to multiple
destinations, then that must be done via the single
application exhaust.
If exhaust
is null
then the default
exhaust is used. This default does nothing with the given
message.
exhaust
- message exhaust message. Passing in
null
results in the default exhaust being used.public static void createDispatcher(EConfigure.Dispatcher dispatcher)
-Dnet.sf.eBus.config.jsonFile=<config file path>
.
This technique guarantees that eBus Dispatchers are in
place prior to assigned eBus clients to a Dispatcher.
But if this is not possible, then createDispatcher
may be used to create a Dispatcher dynamically. But care
must be taken to make sure this is done prior to
registering
an eBus
client to that Dispatcher.
See EConfigure.Dispatcher
and
EConfigure.DispatcherBuilder
for detailed information on how dispatchers work and how
to build a dispatcher configuration.
dispatcher
- dispatcher configuration.java.lang.NullPointerException
- if dispatcher
is null
.java.lang.IllegalStateException
- if a dispatcher named
EConfigure.Dispatcher.name()
already exists.EConfigure.Dispatcher
,
EConfigure.DispatcherBuilder
,
EConfigure.dispatcherBuilder()
public static void register(EObject client)
client
with
eBus, assigning the client to the dispatcher configured
for the client's class, and using the defined
EObject.startup()
and EObject.shutdown()
methods. Once registered, client
may be
started using the startup(EObject)
or
startup(List)
methods.
Note: this method must be called
before client
opens any feeds. Failure to
do so results in a thrown IllegalStateException
.
client
- register this application object with
eBus.java.lang.NullPointerException
- if client
is null
.java.lang.IllegalStateException
- if client
is already registered with eBus. This
will happen if client
has opened any feeds prior
to making this call.register(EObject, String)
,
register(EObject, String, Runnable, Runnable)
,
startup(EObject)
,
shutdown(EObject)
public static void register(EObject client, java.lang.String dispatcherName)
client
with
eBus, assigning client
to the named dispatcher.
This method allows individual application objects to be
assigned to a dispatcher rather than by class. The purpose
here is to allow objects within a class to be assigned to
different dispatchers based on application need. That is,
certain objects may be assigned to a higher priority
dispatcher and the rest assigned to a lower priority
dispatcher.
Once registered, client
may be started by calling
startup(EObject)
or
startup(List)
methods which, in turn,
calls the defined EObject.startup()
method.
Note: this method must be called
before client
opens any feeds. Failure to
do so results in a thrown IllegalStateException
.
client
- register this application client with eBus.dispatcherName
- the dispatcher name.java.lang.NullPointerException
- if client
is null
or
dispatcherName
is null
java.lang.IllegalArgumentException
- if dispatcherName
is empty or does not reference a
configured dispatcher.java.lang.IllegalStateException
- if client
is already registered with eBus. This
will happen if client
has opened any feeds prior
to making this call.public static void register(EObject client, java.lang.String dispatcherName, java.lang.Runnable startCb, java.lang.Runnable shutdownCb)
client
with
eBus, assigning client
to the named dispatcher.
This method allows individual application objects to be
assigned to a dispatcher rather than by class. The purpose
here is to allow objects within a class to be assigned to
different dispatchers based on application need. That is,
certain objects may be assigned to a higher priority
dispatcher and the rest assigned to a lower priority
dispatcher.
Once registered, client
may be started by calling
startup(EObject)
or
startup(List)
methods which, in turn,
calls startCb
.
Note: this method must be called
before client
opens any feeds. Failure to
do so results in a thrown IllegalStateException
.
client
- register this application client with eBus.dispatcherName
- the dispatcher name.startCb
- client
start-up method callback.shutdownCb
- client
shutdown method callback.java.lang.NullPointerException
- if any of the arguments is null
.java.lang.IllegalArgumentException
- if dispatcherName
is either an empty string or
does not reference a known dispatcher.java.lang.IllegalStateException
- if client
is already registered with eBus. This
will happen if client
has opened any feeds prior
to making this call.public static void startup(EObject client)
registered
with eBus if-and-only-if the application object is not
currently started. If application object is started, then
the start-up method will not be called again.
Note: it is possible to start an application object multiple times if that object is shut down between each start.
The start-up method is called from within the context of the object's run queue. Because the application object is started on an eBus thread, the start-up method will not be run concurrently with any other eBus callback.
client
- start this eBus client.java.lang.NullPointerException
- if client
is null
.java.lang.IllegalStateException
- if client
is not registered with eBus.register(EObject)
,
register(EObject, String)
,
register(EObject, String, Runnable, Runnable)
,
startup(List)
,
startupAll()
,
shutdown(EObject)
,
shutdown(List)
,
shutdownAll()
public static void startup(java.util.List<? extends EObject> clients)
clients
if-and-only-if
the application object is not currently started. If any
of the applications is currently started, then that
objects start-up method will not be called again.
Note: it is possible to start an application object multiple times if that object is shut down between each start.
The start-up method is called from within the context of the object's run queue. Because the application object is started on an eBus thread, the start-up method will not be run concurrently with any other eBus callback.
clients
- start these eBus clients.java.lang.NullPointerException
- if clients
is null
or contains a
null
entry.java.lang.IllegalStateException
- if clients
contains an entry that is not
registered with eBus.register(EObject)
,
register(EObject, String)
,
register(EObject, String, Runnable, Runnable)
,
startup(EObject)
,
startupAll()
,
shutdown(EObject)
,
shutdown(List)
,
shutdownAll()
public static void startupAll()
static main
method after having created and
registered the application's eBus objects.
Note: it is possible to start an application object multiple times if that object is shut down between each start.
The start-up method is called from within the context of the object's run queue. Because the application object is started on an eBus thread, the start-up method will not be run concurrently with any other eBus callback.
public static void shutdown(EObject client)
registered
with eBus if-and-only-if the application object is
currently started. If application object is not started,
then the shutdown method will not be called again.
Note: it is possible to shut down an application object multiple times if that object is started up between each shutdown.
The shutdown method is called from within the context of the object's run queue. Because the application object is stopped on an eBus thread, the shutdown method will not be run concurrently with any other eBus callback.
client
- stop this eBus client.java.lang.NullPointerException
- if client
is null
.java.lang.IllegalStateException
- if client
is not registered with eBus.register(EObject)
,
register(EObject, String)
,
register(EObject, String, Runnable, Runnable)
,
startup(EObject)
,
startup(List)
,
startupAll()
,
shutdown(List)
,
shutdownAll()
public static void shutdown(java.util.List<? extends EObject> clients)
clients
if-and-only-if
the application object is currently started. If any
of the applications is not currently started, then that
objects shutdown method will not be called again.
Note: it is possible to shut down an application object multiple times if that object is started up between each shutdown.
The shutdown method is called from within the context of the object's run queue. Because the application object is stopped on an eBus thread, the shutdown method will not be run concurrently with any other eBus callback.
clients
- stop these eBus clients.java.lang.NullPointerException
- if clients
is null
or contains a
null
entry.java.lang.IllegalStateException
- if clients
contains an entry that is not
registered with eBus.register(EObject)
,
register(EObject, String)
,
register(EObject, String, Runnable, Runnable)
,
startup(EObject)
,
startup(List)
,
startupAll()
,
shutdown(EObject)
,
shutdownAll()
public static void shutdownAll()
Note: it is possible to stop an application object multiple times if that object is started between each shut down.
The shutdown method is called from within the context of the object's run queue. Because the application object is stopped on an eBus thread, the start-up method will not be run concurrently with any other eBus callback.
public static void storeKeys(java.io.ObjectOutputStream oos) throws java.io.IOException
Caller is responsible for opening oos
prior to
calling this method and closing oos
after this
method returns.
oos
- load message keys to this object output stream.java.io.IOException
- if an error occurs writing message keys to oos
.storeKeys(Class, ObjectOutputStream)
,
storeKeys(Class, Pattern, ObjectOutputStream)
,
loadKeys(ObjectInputStream)
public static void storeKeys(java.lang.Class<? extends EMessage> mc, java.io.ObjectOutputStream oos) throws java.io.IOException
Caller is responsible for opening oos
prior to
calling this method and closing oos
after this
method returns.
mc
- store message keys with this message class.oos
- load message keys to this object output stream.java.io.IOException
- if an error occurs writing message keys to oos
.storeKeys(ObjectOutputStream)
,
storeKeys(Class, Pattern, ObjectOutputStream)
,
loadKeys(ObjectInputStream)
public static void storeKeys(java.lang.Class<? extends EMessage> mc, Pattern query, java.io.ObjectOutputStream oos) throws java.io.IOException
Caller is responsible for opening oos
prior to
calling this method and closing oos
after this
method returns.
mc
- store message keys with this message class.query
- store message keys with a subject matching
this regular expression.oos
- load message keys to this object output stream.java.io.IOException
- if an I/O error occurs when storing the message keys.storeKeys(ObjectOutputStream)
,
storeKeys(Class, ObjectOutputStream)
,
loadKeys(ObjectInputStream)
public static void loadKeys(java.io.ObjectInputStream ois) throws java.io.IOException
message keys
contained in
the given object input stream and loads them back into the
eBus message key dictionary. eBus subjects are re-created
but not their associated feeds. The application is
responsible for re-opening feeds when the application
starts.
Message keys defined prior to calling this method are not overwritten or replaced by duplicates loaded from the object input stream.
Caller is responsible for opening ois
prior to
calling this method and closing ois
after this
method returns.
ois
- read in message keys from this object input
stream.java.io.IOException
- if an I/O error occurs reading in theprotected final boolean isOverridden(java.lang.String methodName, java.lang.Class<?>... params)
true
if the application object stored in
EClient
defines a method with the given name and
parameters. Returns false
if the application
object does not define the method or inherits a default
implementation of the method.methodName
- method name.params
- method parameters.true
if clazz
overrides the
method.protected static void checkScopes(EMessageKey key, EFeed.FeedScope scope)
IllegalArgumentException
.key
- message key.scope
- feed scope.java.lang.IllegalArgumentException
- if key
scope is local-only and scope
is
not FeedScope.LOCAL_ONLY
.protected static int subclassDistance(java.lang.Class<?> subclass, java.lang.Class<?> bc)
subclass
- check if this class is descended from
the base class.bc
- base class.subclass
is not descended from bc
.Copyright © 2001 - 2024. Charles W. Rapp. All rights reserved.