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

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, 2019. 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.EReplyMessage.ReplyStatus;
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 data. // //----------------------------------------------------------- // 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"; //----------------------------------------------------------- // Statics. // /** * Responsible for publishing monitor notification messages. */ private static final MonitorPublisher sPublisher; /** * Used to synchronize access to static data. */ private static final Lock sMonitorLock = new ReentrantLock(true); /** * Used to assign a unique identifier to a monitored object. */ private static final IndexPool sMonitorIdPool = 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 sReplier; /** * Maps the monitored object to its entry. */ private static final ConcurrentMap sMonitoredMap = new ConcurrentHashMap<>(); /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(Monitor.class.getName()); // 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); sPublisher = new MonitorPublisher(signal); EFeed.register(sPublisher); // Instantiate the monitored object request handler and // advertise it. sReplier = new MonitorReplier(signal); EFeed.register(sReplier); EFeed.startup(sPublisher); EFeed.startup(sReplier); // Wait here for the publisher and replier start-up to // complete. try { signal.await(); } catch (InterruptedException interrupt) {} } // end of static. //--------------------------------------------------------------- // 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 { sPublisher.publish(ApplicationInfo.builder() .appName(name) .appVersion(version) .attributes(attributes) .build()); 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 = MonitorId.builder() .typeName(typeName) .instanceName(instanceName) .id(sMonitorIdPool.nextIndex()) .build(); final MonitorEntry entry = new MonitorEntry(obj, monitorId); // Is this object already registered? if (sMonitoredMap.putIfAbsent(obj, entry) == null) { // No. Continue with the registration. if (sLogger.isLoggable(Level.FINE) == true) { sLogger.fine( String.format( "Registering %s.%s for monitoring.", typeName, instanceName)); } sMonitoredMap.put(obj, entry); // Have the entry open the persistent and // transient status feeds. (entry.mLock).lock(); try { EFeed.register(entry); EFeed.startup(entry); // Wait here for the entry to start up to // complete. try { (entry.mStartSignal).await(); } catch (InterruptedException interrupt) {} } finally { (entry.mLock).unlock(); } // Tell the world about this new monitored object. sPublisher.publish( MonitorUpdate.builder() .instance(monitorId) .updateFlag(true) .build()); } // 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 = sMonitoredMap.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 = sMonitoredMap.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 = sMonitoredMap.remove(obj)) != null) { // Yes. Continue with the deregistration. final MonitorId monitorId = entry.monitorId(); if (sLogger.isLoggable(Level.FINE) == true) { sLogger.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.mLock).lock(); try { EFeed.shutdown(entry); // Wait here for the entry to shutdown to // complete. try { (entry.mStopSignal).await(); } catch (InterruptedException interrupt) {} } finally { (entry.mLock).unlock(); } // Tell the world about this change. sPublisher.publish( MonitorUpdate.builder() .instance(monitorId) .updateFlag(false) .build()); } return; } // end of deregister(Monitorable) //--------------------------------------------------------------- // 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 data. // //------------------------------------------------------- // Locals. // /** * The monitored object. Hold the reference to prevent * its disappearing. */ private final Monitorable mObject; /** * The monitored object's identifier. */ private final MonitorId mId; /** * This lock is acquired when starting and shutting down * a monitor. */ private final Lock mLock; /** * Set this signal when {@link #startup()} completes. */ private final Condition mStartSignal; /** * Set this signal when {@link #shutdown()} completes. */ private final Condition mStopSignal; /** * The current persistent status level. Initialized to * {@link ActionLevel#NO_ACTION}. */ private ActionLevel mPersistLevel; /** * The current persistent action name. Initialized to * "Registered". */ private String mPersistName; /** * The current persistent action message. Initialized to * "". */ private String mPersistMessage; /** * Publish persistent status messages on this feed. */ private EPublishFeed mPersistFeed; /** * Publish transient status messages on this feed. */ private EPublishFeed mTransientFeed; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // public MonitorEntry(final Monitorable object, final MonitorId id) { mObject = object; mId = id; mLock = new ReentrantLock(); mStartSignal = mLock.newCondition(); mStopSignal = mLock.newCondition(); mPersistLevel = ActionLevel.NO_ACTION; mPersistName = "Registered"; mPersistMessage = "Registered with monitor subsystem"; mPersistFeed = null; mTransientFeed = null; } // end of MonitorEntry(Monitorable, MonitorId) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // public Monitorable monitoredObject() { return (mObject); } // end of monitoredObject() public MonitorId monitorId() { return (mId); } // 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() { mLock.lock(); try { final String subject = mId.toString(); final EMessageKey persistKey = new EMessageKey( PersistentStatusMessage.class, subject); final EMessageKey transientKey = new EMessageKey( TransientStatusMessage.class, subject); mPersistFeed = EPublishFeed.open(this, persistKey, FeedScope.LOCAL_AND_REMOTE); mTransientFeed = EPublishFeed.open(this, transientKey, FeedScope.LOCAL_AND_REMOTE); mPersistFeed.advertise(); mTransientFeed.advertise(); mPersistFeed.updateFeedState(EFeedState.UP); mTransientFeed.updateFeedState(EFeedState.UP); mStartSignal.signal(); } finally { mLock.unlock(); } return; } // end of startup() /** * Closes both the persistent and transient status feeds * if open. * @see #startup() */ @Override public void shutdown() { mLock.lock(); try { if (mPersistFeed != null) { mPersistFeed.close(); mPersistFeed = null; } if (mTransientFeed != null) { mTransientFeed.close(); mTransientFeed = null; } mStopSignal.signal(); } finally { mLock.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 == mPersistFeed && feedState == EFeedState.UP) { // Up. Post the current persistent state. mPersistFeed.publish( PersistentStatusMessage.builder() .subject(mId.toString()) .instance(mId.id) .actionLevel(mPersistLevel) .actionName(mPersistName) .actionMessage(mPersistMessage) .build()); } 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) { mPersistLevel = level; mPersistName = actionName; mPersistMessage = actionMsg; if (mPersistFeed.isFeedUp() == true) { mPersistFeed.publish( PersistentStatusMessage.builder() .subject(mId.toString()) .instance(mId.id) .actionLevel(mPersistLevel) .actionName(mPersistName) .actionMessage(mPersistMessage) .build()); } 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 (mTransientFeed.isFeedUp() == true) { mTransientFeed.publish( TransientStatusMessage.builder() .subject(mId.toString()) .instance(mId.id) .actionLevel(level) .actionName(actionName) .actionMessage(actionMsg) .build()); } return; } // end of updateTransient(ActionLevel, String, String) } // 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 data. // //------------------------------------------------------- // Locals. // /** * Publish application information on this feed. */ private EPublishFeed mAppInfoFeed; /** * Publish monitored state updates on this feed. */ private EPublishFeed mUpdateFeed; /** * Decrement this signal when start-up is complete and * then drop it. */ private CountDownLatch mSignal; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new monitor notification publisher. * @param signal decrement this signal when start-up * completes. */ private MonitorPublisher(final CountDownLatch signal) { mSignal = 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. mAppInfoFeed = EPublishFeed.open( this, appInfoKey, FeedScope.LOCAL_AND_REMOTE); mUpdateFeed = EPublishFeed.open( this, updateKey, FeedScope.LOCAL_AND_REMOTE); mAppInfoFeed.advertise(); mUpdateFeed.advertise(); mAppInfoFeed.updateFeedState(EFeedState.UP); mUpdateFeed.updateFeedState(EFeedState.UP); mSignal.countDown(); mSignal = 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 (mAppInfoFeed.isFeedUp() == true) { mAppInfoFeed.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 (mUpdateFeed.isFeedUp() == true) { mUpdateFeed.publish(msg); } return; } // end of publish(MonitorUpdate) } // end of class MonitorPublisher /** * This class replies to {@link MonitoredObjectRequest} * messages. */ private static final class MonitorReplier implements EReplier { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Decrement this signal when start-up is complete and * then drop it. */ private CountDownLatch mSignal; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a monitor request handler instance. * @param signal decrement this signal when start-up * completes. */ private MonitorReplier(final CountDownLatch signal) { mSignal = 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); mSignal.countDown(); mSignal = 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; sMonitorLock.lock(); try { int i = 0; ids = new MonitorId[sMonitoredMap.size()]; for (MonitorEntry entry : sMonitoredMap.values()) { ids[i] = entry.monitorId(); ++i; } } finally { sMonitorLock.unlock(); } request.reply( MonitoredObjectReply.builder() .subject((request.key()).subject()) .replyStatus(ReplyStatus.OK_FINAL) .instances(ids) .build()); 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. //------------------------------------------------------- } // end of class MonitorReplier } // end of class Monitor





© 2015 - 2024 Weber Informatics LLC | Privacy Policy