net.sf.eBusx.monitor.Monitor 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 2011, 2012, 2015, 2016. Charles W. Rapp
// All Rights Reserved.
//
package net.sf.eBusx.monitor;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EFeed;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.EPublishFeed;
import net.sf.eBus.client.EPublisher;
import net.sf.eBus.client.EReplier;
import net.sf.eBus.client.EReplyFeed;
import net.sf.eBus.client.EReplyFeed.ERequest;
import net.sf.eBus.client.IEPublishFeed;
import net.sf.eBus.messages.EField;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.util.IndexPool;
/**
* This class provides static methods, interfacing with the eBus
* monitoring subsystem. {@link Monitorable} objects must
* {@link #register(net.sf.eBusx.monitor.Monitorable) register}
* prior to calling
* {@link #update(ActionLevel, String, String, Monitorable) updating}
* its current state or reporting
* {@link #transientStatus(ActionLevel, String, String, Monitorable) transient events}.
* When monitorable objects
* {@link #deregister(Monitorable) deregisters}, the monitor
* status is retracted.
*
* This class publishes the optional
* {@link #applicationInfo(String, String, String, String, EField) application information}.
* This information is generally set once upon application
* start and not changed although that is allowed. This class
* also publishes the
* {@link MonitorUpdate} notification which reports to
* subscribers newly registered or deregistered monitorable
* objects.
* Finally, this class sends
* {@link MonitoredObjectReply} replies to
* {@link MonitoredObjectRequest} messages. These replies contain
* the list of currently register monitorable objects.
*
* @author Charles Rapp
*/
public final class Monitor
{
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* A private default constructor is defined to prevent
* instantiation.
*/
private Monitor()
{}
//
// end of Constructors.
//-----------------------------------------------------------
/**
* Publishes the given application information.
* @param name the application name. May not be {@code null}
* or empty.
* @param version the application version. May not be
* {@code null} or empty.
* @param copyright the application copyright. May be
* {@code null}.
* @param description the application description. May be
* {@code null}.
* @param attributes the application-specific attributes.
* @throws IllegalArgumentException
* if either {@code name} or {@code version} is {@code null}
* or empty.
*/
public static void applicationInfo(final String name,
final String version,
final String copyright,
final String description,
final EField attributes)
throws IllegalArgumentException
{
_publisher.publish(new ApplicationInfo(name,
version,
copyright,
name,
attributes));
return;
} // end of applicationInfo(String, String, String, String)
/**
* Registers a monitorable object. The object's initial
* persistent status is set to {@link ActionLevel#NO_ACTION},
* "Registered". A {@link Monitorable} object must
* successfully register prior to
* {@link #update(ActionLevel, String, String, Monitorable) updating}
* is status or posting a
* {@link #transientStatus(ActionLevel, String, String, Monitorable) transient} event.
* @param obj register this monitorable object.
* @exception IllegalArgumentException
* if {@code obj} is {@code null} or
* {@link Monitorable#instanceName} returns a {@code null}
* or empty string.
* @see #update(ActionLevel, String, String, Monitorable)
* @see #transientStatus(ActionLevel, String, String, Monitorable)
* @see #deregister(Monitorable)
*/
public static void register(final Monitorable obj)
throws IllegalArgumentException
{
final String instanceName;
if (obj == null)
{
throw (new IllegalArgumentException("null obj"));
}
else if ((instanceName = obj.instanceName()) == null ||
instanceName.length() == 0)
{
throw (
new IllegalArgumentException(
"null or empty instance name"));
}
else
{
final String typeName = (obj.getClass()).getName();
final MonitorId monitorId =
new MonitorId(typeName,
instanceName,
_monitorIdPool.nextIndex());
final MonitorEntry entry =
new MonitorEntry(obj, monitorId);
// Is this object already registered?
if (_monitoredMap.putIfAbsent(obj, entry) == null)
{
// No. Continue with the registration.
if (_logger.isLoggable(Level.FINE) == true)
{
_logger.fine(
String.format(
"Registering %s.%s for monitoring.",
typeName,
instanceName));
}
_monitoredMap.put(obj, entry);
// Have the entry open the persistent and
// transient status feeds.
(entry._lock).lock();
try
{
EFeed.register(entry);
EFeed.startup(entry);
// Wait here for the entry to start up to
// complete.
try
{
(entry._startSignal).await();
}
catch (InterruptedException interrupt)
{}
}
finally
{
(entry._lock).unlock();
}
// Tell the world about this new monitored object.
_publisher.publish(
new MonitorUpdate(
Monitor.MONITOR_UPDATE_SUBJECT,
monitorId,
true));
}
// Yes, ignore this duplicate registration.
}
return;
} // end of register(Monitorable)
/**
* Updates the monitorable object on-going status with the
* given parameters.
* @param actionLevel the action level.
* @param actionName the action name.
* @param actionMsg the human-readable action message.
* @param obj the registered monitorable object.
* @throws IllegalArgumentException
* if {@code obj} is {@code null} or {@code actionName} is
* either {@code null} or an empty string.
* @throws IllegalStateException
* if {@code obj} is not registered.
* @see #transientStatus(ActionLevel, String, String, Monitorable)
* @see #register(Monitorable)
*/
public static void update(final ActionLevel actionLevel,
final String actionName,
final String actionMsg,
final Monitorable obj)
throws IllegalArgumentException,
IllegalStateException
{
final MonitorEntry entry;
if (obj == null)
{
throw (new IllegalArgumentException("null obj"));
}
else if (actionName == null || actionName.length() == 0)
{
throw (
new IllegalArgumentException(
"null or empty actionName"));
}
// The actionMsg may be null or empty.
// But the monitored object must be registered.
else if ((entry = _monitoredMap.get(obj)) == null)
{
throw (
new IllegalStateException("obj not registered"));
}
else
{
entry.updatePersistent(actionLevel,
actionName,
actionMsg);
}
return;
} // end of updatePersistent(ActionLevel, String, String, Monitorable)
/**
* Posts a one-time transient event for the given monitorable
* object.
* @param actionLevel the action level.
* @param actionName the action name.
* @param actionMsg the human-readable action message.
* @param obj the registered monitorable object.
* @throws IllegalArgumentException
* if {@code obj} is {@code null} or {@code actionName} is
* either {@code null} or an empty string.
* @throws IllegalStateException
* if {@code obj} is not registered.
* @see #register(Monitorable)
* @see #update(ActionLevel, String, String, Monitorable)
*/
public static
void transientStatus(final ActionLevel actionLevel,
final String actionName,
final String actionMsg,
final Monitorable obj)
throws IllegalArgumentException,
IllegalStateException
{
final MonitorEntry entry;
if (obj == null)
{
throw (new IllegalArgumentException("null obj"));
}
else if (actionName == null || actionName.length() == 0)
{
throw (
new IllegalArgumentException(
"null or empty actionName"));
}
// The actionMsg may be null or empty.
// But the monitored object must be registered.
else if ((entry = _monitoredMap.get(obj)) == null)
{
throw (
new IllegalStateException("obj not registered"));
}
else
{
entry.updateTransient(actionLevel,
actionName,
actionMsg);
}
return;
} // end of transientStatus(...)
/**
* Removes a registered {@link Monitorable} object from the
* monitor subsystem, retracting its published status and
* transient event feeds.
* @param obj remove this monitorable instance.
* @throws IllegalArgumentException
* if {@code obj} is {@code null}.
*/
public static void deregister(final Monitorable obj)
throws IllegalArgumentException
{
final MonitorEntry entry;
// Is this monitored object currently registered?
if (obj == null)
{
throw (new IllegalArgumentException("obj is null"));
}
else if ((entry = _monitoredMap.remove(obj)) != null)
{
// Yes. Continue with the deregistration.
final MonitorId monitorId = entry.monitorId();
if (_logger.isLoggable(Level.FINE) == true)
{
_logger.fine(
String.format(
"Deregistering %s from monitoring.",
monitorId));
}
// Publish and close the monitor entry.
entry.updatePersistent(
ActionLevel.NO_ACTION,
"Deregistered",
"Deregistered from monitor subsystem");
(entry._lock).lock();
try
{
EFeed.shutdown(entry);
// Wait here for the entry to shutdown to
// complete.
try
{
(entry._stopSignal).await();
}
catch (InterruptedException interrupt)
{}
}
finally
{
(entry._lock).unlock();
}
// Tell the world about this change.
_publisher.publish(
new MonitorUpdate(Monitor.MONITOR_UPDATE_SUBJECT,
monitorId,
false));
}
return;
} // end of deregister(Monitorable)
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Statics.
//
/**
* Responsible for publishing monitor notification messages.
*/
private static final MonitorPublisher _publisher;
/**
* Used to synchronize access to static data.
*/
private static final Lock _monitorLock =
new ReentrantLock(true);
/**
* Used to assign a unique identifier to a monitored object.
*/
private static final IndexPool _monitorIdPool =
new IndexPool();
/**
* Since eBus only interacts with objects, create an instance
* of the static {@link MonitorReplier} class to handle
* {@code net.sf.eBusx.monitor.MonitoredObjectRequest}
* messages.
*/
private static final MonitorReplier _replier;
/**
* Maps the monitored object to its entry.
*/
private static final ConcurrentMap
_monitoredMap = new ConcurrentHashMap<>();
/**
* Logging subsystem interface.
*/
private static final Logger _logger =
Logger.getLogger(Monitor.class.getName());
//-----------------------------------------------------------
// Constants.
//
/**
* {@link #MONITOR_UPDATE_SUBJECT} is advertised with the
* subject "net.sf.eBusx.monitor" to form this notification
* key.
*/
public static final String MONITOR_UPDATE_SUBJECT =
"net.sf.eBusx.monitor";
/**
* {@link #MONITOR_REQUEST} is advertised with the subject
* "net.sf.eBusx.monitor" to form this request key.
*/
public static final String MONITOR_REQUEST =
"net.sf.eBusx.monitor.MonitoredObjectRequest";
// Static initialization block.
static
{
// "Compile" the message types on start.
DataType.findType(ApplicationInfo.class);
DataType.findType(MonitorUpdate.class);
DataType.findType(MonitoredObjectReply.class);
DataType.findType(MonitoredObjectRequest.class);
DataType.findType(PersistentStatusMessage.class);
DataType.findType(TransientStatusMessage.class);
// Instantiate the monitor object publisher and advertise
// its notification feed.
final CountDownLatch signal = new CountDownLatch(2);
_publisher = new MonitorPublisher(signal);
EFeed.register(_publisher);
// Instantiate the monitored object request handler and
// advertise it.
_replier = new MonitorReplier(signal);
EFeed.register(_replier);
EFeed.startup(_publisher);
EFeed.startup(_replier);
// Wait here for the publisher and replier start-up to
// complete.
try
{
signal.await();
}
catch (InterruptedException interrupt)
{}
} // end of static.
//---------------------------------------------------------------
// Inner classes.
//
/**
* This passive, immutable class is used to encapsulate a
* registered {@link Monitorable} object, its {@link MonitorId}
* and latest PersistentStatus message.
*/
private static class MonitorEntry
implements EPublisher
{
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
public MonitorEntry(final Monitorable object,
final MonitorId id)
{
_object = object;
_id = id;
_lock = new ReentrantLock();
_startSignal = _lock.newCondition();
_stopSignal = _lock.newCondition();
_persistLevel = ActionLevel.NO_ACTION;
_persistName = "Registered";
_persistMessage =
"Registered with monitor subsystem";
_persistFeed = null;
_transientFeed = null;
} // end of MonitorEntry(Monitorable, MonitorId)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get methods.
//
public Monitorable monitoredObject()
{
return (_object);
} // end of monitoredObject()
public MonitorId monitorId()
{
return (_id);
} // end of monitorId()
//
// end of Get methods.
//-------------------------------------------------------
//-------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Opens, advertises, and sets the feed state to up for
* both the persistent and transient notification feeds.
* @see #shutdown()
*/
@Override
public void startup()
{
_lock.lock();
try
{
final String subject = _id.toString();
final EMessageKey persistKey =
new EMessageKey(
PersistentStatusMessage.class, subject);
final EMessageKey transientKey =
new EMessageKey(
TransientStatusMessage.class, subject);
_persistFeed =
EPublishFeed.open(this,
persistKey,
FeedScope.LOCAL_AND_REMOTE);
_transientFeed =
EPublishFeed.open(this,
transientKey,
FeedScope.LOCAL_AND_REMOTE);
_persistFeed.advertise();
_transientFeed.advertise();
_persistFeed.updateFeedState(EFeedState.UP);
_transientFeed.updateFeedState(EFeedState.UP);
_startSignal.signal();
}
finally
{
_lock.unlock();
}
return;
} // end of startup()
/**
* Closes both the persistent and transient status feeds
* if open.
* @see #startup()
*/
@Override
public void shutdown()
{
_lock.lock();
try
{
if (_persistFeed != null)
{
_persistFeed.close();
_persistFeed = null;
}
if (_transientFeed != null)
{
_transientFeed.close();
_transientFeed = null;
}
_stopSignal.signal();
}
finally
{
_lock.unlock();
}
return;
} // end of shutdown()
//
// end of EObject Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// EPublisher Interface Implementation.
//
/**
* Informs this publisher that the persistent status feed
* is either up or down. If {@code feedState} is
* {@link EFeedState#UP up}, then sends the current
* persistent state.
* @param feedState the notification feed is either up or
* down.
* @param feed {@code feedState} applies to this
* persistent state feed.
*/
@Override
public void publishStatus(final EFeedState feedState,
final IEPublishFeed feed)
{
// Is the feed up or down?
if (feed == _persistFeed &&
feedState == EFeedState.UP)
{
// Up. Post the current persistent state.
_persistFeed.publish(
new PersistentStatusMessage(_id.toString(),
_id.id,
_persistLevel,
_persistName,
_persistMessage));
}
return;
} // end of persistStatus(EFeedState, IEPublishFeed}
//
// end of EPublisher Interface Implementation.
//-------------------------------------------------------
/**
* Updates the monitored objects current persistent
* status information. Posts the persistent status
* message if the feed is up.
* @param level current persistent action level.
* @param actionName current persistent action name.
* @param actionMsg current persistent action message.
*/
private void updatePersistent(final ActionLevel level,
final String actionName,
final String actionMsg)
{
_persistLevel = level;
_persistName = actionName;
_persistMessage = actionMsg;
if (_persistFeed.isFeedUp() == true)
{
_persistFeed.publish(
new PersistentStatusMessage(_id.toString(),
_id.id,
_persistLevel,
_persistName,
_persistMessage));
}
return;
} // end of updatePersistent(ActionLevel, String, String)
/**
* Publishes the transient status message is the feed is
* up.
* @param level current transient action level.
* @param actionName current transient action name.
* @param actionMsg current transient action message.
*/
private void updateTransient(final ActionLevel level,
final String actionName,
final String actionMsg)
{
if (_transientFeed.isFeedUp() == true)
{
_transientFeed.publish(
new TransientStatusMessage(_id.toString(),
_id.id,
level,
actionName,
actionMsg));
}
return;
} // end of updateTransient(ActionLevel, String, String)
//-----------------------------------------------------------
// Member data.
//
/**
* The monitored object. Hold the reference to prevent
* its disappearing.
*/
private final Monitorable _object;
/**
* The monitored object's identifier.
*/
private final MonitorId _id;
/**
* This lock is acquired when starting and shutting down
* a monitor.
*/
private final Lock _lock;
/**
* Set this signal when {@link #startup()} completes.
*/
private final Condition _startSignal;
/**
* Set this signal when {@link #shutdown()} completes.
*/
private final Condition _stopSignal;
/**
* The current persistent status level. Initialized to
* {@link ActionLevel#NO_ACTION}.
*/
private ActionLevel _persistLevel;
/**
* The current persistent action name. Initialized to
* "Registered".
*/
private String _persistName;
/**
* The current persistent action message. Initialized to
* "".
*/
private String _persistMessage;
/**
* Publish persistent status messages on this feed.
*/
private EPublishFeed _persistFeed;
/**
* Publish transient status messages on this feed.
*/
private EPublishFeed _transientFeed;
} // end of class MonitorEntry
/**
* The singleton application-level publisher for the JVM.
* Publishes the application information and monitor update
* messages.
*/
private static final class MonitorPublisher
implements EPublisher
{
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new monitor notification publisher.
* @param signal decrement this signal when start-up
* completes.
*/
private MonitorPublisher(final CountDownLatch signal)
{
_signal = signal;
} // end of MonitorPublisher(CountDownLatch)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Opens and advertises the publisher feeds. Once open,
* these feeds are not closed.
*/
@Override
public void startup()
{
final EMessageKey appInfoKey =
new EMessageKey(ApplicationInfo.class,
MONITOR_UPDATE_SUBJECT);
final EMessageKey updateKey =
new EMessageKey(
MonitorUpdate.class, MONITOR_UPDATE_SUBJECT);
// Put the publisher feeds in place.
_appInfoFeed =
EPublishFeed.open(
this, appInfoKey, FeedScope.LOCAL_AND_REMOTE);
_updateFeed =
EPublishFeed.open(
this, updateKey, FeedScope.LOCAL_AND_REMOTE);
_appInfoFeed.advertise();
_updateFeed.advertise();
_appInfoFeed.updateFeedState(EFeedState.UP);
_updateFeed.updateFeedState(EFeedState.UP);
_signal.countDown();
_signal = null;
return;
} // end of startup()
// Since this publisher is never shutdown, the shutdown()
// method is not overridden.
//
// end of EObject Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// EPublisher Interface Implementation.
//
/**
* Informs this publisher that the feed is either up
* or down. Since the feed state is checked each time
* a message is published, this method does nothing.
* @param feedState the notification feed is either up or
* down.
* @param feed {@code feedState} applies to this
* notification feed.
*/
@Override
public void publishStatus(final EFeedState feedState,
final IEPublishFeed feed)
{}
//
// end of EPublisher Interface Implementation.
//-------------------------------------------------------
/**
* Publishes the application information message if the
* feed is up.
* @param msg application information message.
* @see #publish(MonitorUpdate)
*/
private void publish(final ApplicationInfo msg)
{
if (_appInfoFeed.isFeedUp() == true)
{
_appInfoFeed.publish(msg);
}
return;
} // end of publish(ApplicationInfo)
/**
* Publishes the monitor updatePersistent message is the feed is
up.
* @param msg monitor updatePersistent message.
* @see #publish(ApplicationInfo)
*/
private void publish(final MonitorUpdate msg)
{
if (_updateFeed.isFeedUp() == true)
{
_updateFeed.publish(msg);
}
return;
} // end of publish(MonitorUpdate)
//-----------------------------------------------------------
// Member data.
//
/**
* Publish application information on this feed.
*/
private EPublishFeed _appInfoFeed;
/**
* Publish monitored state updates on this feed.
*/
private EPublishFeed _updateFeed;
/**
* Decrement this signal when start-up is complete and
* then drop it.
*/
private CountDownLatch _signal;
} // end of class MonitorPublisher
/**
* This class replies to {@link MonitoredObjectRequest}
* messages.
*/
private static final class MonitorReplier
implements EReplier
{
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a monitor request handler instance.
* @param signal decrement this signal when start-up
* completes.
*/
private MonitorReplier(final CountDownLatch signal)
{
_signal = signal;
} // end of MonitorReplier(CountDownLatch)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// EObject EPublisher Interface Implementation.
//
/**
* Opens and advertises the monitored object request
* reply feed. Once open, the reply feed remains open.
*/
@Override
public void startup()
{
final EMessageKey requestKey =
new EMessageKey(MonitoredObjectRequest.class,
MONITOR_REQUEST);
final EReplyFeed replyFeed =
EReplyFeed.open(this,
requestKey,
FeedScope.LOCAL_AND_REMOTE,
null);
replyFeed.advertise();
replyFeed.updateFeedState(EFeedState.UP);
_signal.countDown();
_signal = null;
return;
} // end of startup()
// Since this replier is never shutdown, the shutdown()
// method is not overridden.
//
// end of EObject Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// EReplier Interface Implementation.
//
/**
* Generate a monitored object reply to this
* request.
* @param request post the reply to this request.
*/
@Override
public void request(final ERequest request)
{
final MonitorId[] ids;
_monitorLock.lock();
try
{
int i = 0;
ids = new MonitorId[_monitoredMap.size()];
for (MonitorEntry entry : _monitoredMap.values())
{
ids[i] = entry.monitorId();
++i;
}
}
finally
{
_monitorLock.unlock();
}
request.reply(
new MonitoredObjectReply(
(request.key()).subject(), ids));
return;
} // end of request(ERequestb)
/**
* Since a reply is sent as soon as a request is
* received, there is no way it can be canceled.
* @param request cancel this request.
*/
@Override
public void cancelRequest(final ERequest request)
{} // end of cancelRequest(ERequest)
//
// end of EReplier Interface Implementation.
//-------------------------------------------------------
//-----------------------------------------------------------
// Member data.
//
/**
* Decrement this signal when start-up is complete and
* then drop it.
*/
private CountDownLatch _signal;
} // end of class MonitorReplier
} // end of class Monitor