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

net.sf.eBusx.monitor.Monitor Maven / Gradle / Ivy

//
// Copyright 2011, 2012, 2015, 2016, 2019 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package net.sf.eBusx.monitor;

import com.google.common.base.Strings;
import java.util.Objects;
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 de-registered 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"; /** * Monitor entry publisher name. */ private static final String MONITOR_PUBLISHER = "MonitorPublisher"; /** * Monitor entry replier name. */ private static final String MONITOR_REPLIER = "MonitorReplier"; //----------------------------------------------------------- // 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) { sPublisher.publish(ApplicationInfo.builder() .appName(name) .appVersion(version) .copyright(copyright) .description(description) .attributes(attributes) .build()); } // 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. * @throws NullPointerException * if {@code obj} is {@code null}. * @throws 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) { final String instanceName = (Objects.requireNonNull(obj, "obj is null")).instanceName(); if (Strings.isNullOrEmpty(instanceName)) { throw ( new IllegalArgumentException( "instance name is either null or empty")); } 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)) { 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. while (!entry.isRunning()) { 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. } // 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 NullPointerException * if {@code obj} is {@code null}. * @throws IllegalArgumentException * if {@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) * @see #deregister(Monitorable) */ public static void update(final ActionLevel actionLevel, final String actionName, final String actionMsg, final Monitorable obj) { Objects.requireNonNull(obj, "obj is null"); if (Strings.isNullOrEmpty(actionName)) { throw ( new IllegalArgumentException( "null or empty actionName")); } final MonitorEntry entry = sMonitoredMap.get(obj); // The actionMsg may be null or empty. // But the monitored object must be registered. if (entry == null) { throw ( new IllegalStateException("obj not registered")); } entry.updatePersistent(actionLevel, actionName, actionMsg); } // 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 NullPointerException * if {@code obj} is {@code null}. * @throws IllegalArgumentException * if {@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) * @see #deregister(Monitorable) */ public static void transientStatus(final ActionLevel actionLevel, final String actionName, final String actionMsg, final Monitorable obj) { Objects.requireNonNull(obj, "null obj"); if (Strings.isNullOrEmpty(actionName)) { throw ( new IllegalArgumentException( "null or empty actionName")); } final MonitorEntry entry = sMonitoredMap.get(obj); // The actionMsg may be null or empty. // But the monitored object must be registered. if (entry == null) { throw ( new IllegalStateException("obj not registered")); } entry.updateTransient(actionLevel, actionName, actionMsg); } // 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 NullPointerException * if {@code obj} is {@code null}. * * @see #update(ActionLevel, String, String, Monitorable) * @see #transientStatus(ActionLevel, String, String, Monitorable) * @see #register(Monitorable) */ public static void deregister(final Monitorable obj) { Objects.requireNonNull(obj, "null obj"); final MonitorEntry entry = sMonitoredMap.remove(obj); // Is this monitored object currently registered? if (entry != null) { // Yes. Continue with the deregistration. final MonitorId monitorId = entry.monitorId(); if (sLogger.isLoggable(Level.FINE)) { 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. while (entry.isRunning()) { 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()); } } // 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; /** * Set to {@code true} after monitor entry successfully * starts; otherwise set to {@code false}. */ private boolean mRunFlag; /** * 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. // /** * Creates an entry for the given monitorable object and * identifier. * @param object monitored object. * @param id monitor identifier. */ public MonitorEntry(final Monitorable object, final MonitorId id) { mObject = object; mId = id; mRunFlag = false; 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. // /** * Returns monitored object. * @return monitored object. */ public Monitorable monitoredObject() { return (mObject); } // end of monitoredObject() /** * Returns monitored object identifier. * @return monitor identifier. */ public MonitorId monitorId() { return (mId); } // end of monitorId() /** * Returns {@code true} if monitor entry successfully * started and is running; otherwise returns * {@code false}. * @return {@code true} if monitor entry is running. */ public boolean isRunning() { return (mRunFlag); } // end of isRunning() // // end of Get methods. //------------------------------------------------------- //------------------------------------------------------- // EObject Interface Implementation. // /** * Returns monitor entry instance name. * @return instance name. */ @Override public String name() { return (mId.instanceName); } // end of name() /** * 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.builder()) .target(this) .messageKey(persistKey) .scope(FeedScope.LOCAL_AND_REMOTE) .build(); mTransientFeed = (EPublishFeed.builder()) .target(this) .messageKey(transientKey) .scope(FeedScope.LOCAL_AND_REMOTE) .build(); mPersistFeed.advertise(); mTransientFeed.advertise(); mPersistFeed.updateFeedState(EFeedState.UP); mTransientFeed.updateFeedState(EFeedState.UP); mRunFlag = true; mStartSignal.signal(); } finally { mLock.unlock(); } } // 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; } mRunFlag = false; mStopSignal.signal(); } finally { mLock.unlock(); } } // 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()); } } // 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()) { mPersistFeed.publish( PersistentStatusMessage.builder() .subject(mId.toString()) .instance(mId.id) .actionLevel(mPersistLevel) .actionName(mPersistName) .actionMessage(mPersistMessage) .build()); } } // 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()) { mTransientFeed.publish( TransientStatusMessage.builder() .subject(mId.toString()) .instance(mId.id) .actionLevel(level) .actionName(actionName) .actionMessage(actionMsg) .build()); } } // 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. // /** * Returns monitor publisher name. * @return monitor publisher name. */ @Override public String name() { return (MONITOR_PUBLISHER); } // end of name() /** * 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.builder()) .target(this) .messageKey(appInfoKey) .scope(FeedScope.LOCAL_AND_REMOTE) .build(); mUpdateFeed = (EPublishFeed.builder()) .target(this) .messageKey(updateKey) .scope(FeedScope.LOCAL_AND_REMOTE) .build(); 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. */ // This callback does nothing and so the method body is // empty. @SuppressWarnings({"java:S1186"}) @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()) { mAppInfoFeed.publish(msg); } } // 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()) { mUpdateFeed.publish(msg); } } // 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. // /** * Returns monitor replier name. * @return monitor replier name. */ @Override public String name() { return (MONITOR_REPLIER); } // end of name() /** * 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.builder()) .target(this) .messageKey(requestKey) .scope(FeedScope.LOCAL_AND_REMOTE) .build(); 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()); } // 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. * @param mayRespond ignored. */ // This callback does nothing and so the method body is // empty. @SuppressWarnings({"java:S1186"}) @Override public void cancelRequest(final ERequest request, final boolean mayRespond) {} // end of cancelRequest(ERequest) // // end of EReplier Interface Implementation. //------------------------------------------------------- } // end of class MonitorReplier } // end of class Monitor





© 2015 - 2024 Weber Informatics LLC | Privacy Policy