net.sf.eBusx.monitor.Monitor Maven / Gradle / Ivy
//
// Copyright 2011, 2012, 2015, 2016, 2019, 2024 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 com.google.common.collect.ImmutableList;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nullable;
import net.sf.eBus.client.EClient;
import net.sf.eBus.client.EClient.ClientLocation;
import net.sf.eBus.client.EFeed;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.EObject;
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.eBusx.monitor.MonitorUpdate.UpdateType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides the ability to instrument
* {@link EObject eBus objects} and monitor those instrumented
* object's status. The first step is to
* {@link #openMonitor(String, String, String, String, String, EField) open a Monitor}
* instance. Note that only one {@code Monitor} may be opened
* and it stays open as long as the JVM is running. The next
* step is
* {@link #register(EObject) registering eBus objects}. Once
* registered, these objects may
* {@link #update(ActionLevel, String, String, EObject) report on-going status}
* and
* {@link #transientStatus(ActionLevel, String, String, EObject) transient events}.
* When an eBus object is no longer operational, it should be
* {@link #deregister(EObject) de-registered} from
* {@code Monitor}.
*
* Note: {@code Monitor} does not
* maintain a strong reference to registered eBus objects. This
* means that an eBus object can be finalized while still
* registered with {@code Montior}. If this happens,
* {@code Monitor} will automatically de-register the finalized
* eBus object.
*
*
* {@code Monitor} publishes:
*
*
* -
* {@link ApplicationInfo} reporting the monitor's opening,
*
* -
* {@link PersistentStatusMessage} reporting an update to
* an eBus object's on-going status, and
*
* -
* {@link TransientStatusMessage} reporting an eBus object's
* transient event.
*
*
*
* {@code Monitor} also handles the following requests:
*
*
* -
* {@link ApplicationInfoRequest}: request for monitor's
* application information. {@link ApplicationInfoReply} is
* sent in reply.
*
* -
* {@link MonitoredObjectRequest}: request for current
* on-going registered eBus objects.
* {@link MonitoredObjectReply} is sent in reply containing
* {@link PersistentStatusMessage}s.
*
*
*
* Note: there is no way to request previously post transient
* events. These can only be received when actively subscribed
* to transient status events.
*
*
* See {@link net.sf.eBusx.monitor package documentation} for a
* detailed explanation on how to use {@code Monitor}. The
* package documentation provides detailed explanation on how
* to instrument an eBus object and monitor updates.
*
*
* @see #openMonitor(String, String, String, String, String, EField)
* @see #getMonitor()
*
* @author Charles Rapp
*/
@SuppressWarnings({"java:S1450"})
public final class Monitor
implements EObject
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Monitor publisher identifier is {@value}.
*/
public static final long MONITOR_PUBLISHER_ID = -2001L;
/**
* eBus object name is "{@value}".
*/
public static final String MONITOR_NAME = "eBus monitor";
/**
* {@link MonitorUpdate} action name is {@value}.
*/
public static final String REGISTER_ACTION_NAME =
"registered";
/**
* {@link MonitorUpdate} action name is {@value}.
*/
public static final String DEREGISTER_ACTION_NAME =
"deregistered";
/**
* {@code MONITOR_UPDATE_FORMAT} is advertised with the
* subject {@value} with host and application name to form
* this notification key.
*/
public static final String MONITOR_UPDATE_FORMAT =
"/eBus/monitor/update/%s/%s";
/**
* {@link ApplicationInfoRequest} is advertised with the
* subject {@value} to form request key.
*/
public static final String APP_INFO_REQUEST_SUBJECT =
"/eBus/monitor/request/app-info";
/**
* {@link MonitoredObjectRequest} is advertised with the
* subject {@value} to form request key.
*/
public static final String ONGOING_REQUEST_SUBJECT =
"/eBus/monitor/request/on-going";
/**
* Monitor entry publisher name.
*/
private static final String MONITOR_PUBLISHER =
"MonitorPublisher";
/**
* Monitor entry replier name.
*/
private static final String MONITOR_REPLIER =
"MonitorReplier";
//-----------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(Monitor.class);
/**
* Singleton eBus monitor instance. Instantiated using
* {@link #getMonitor()} method.
*/
private static Monitor sMonitor;
/**
* Used to protect access to monitor singleton instance.
*/
private static final Lock sInstanceLock =
new ReentrantLock();
/**
* Finalized {@link MonitorEntry} objects are placed into
* this queue. This queue is processed by the
* {@link #sGcThread clean up thread}.
*/
private static final ReferenceQueue super EObject> sGcQueue;
/**
* This thread uses the blocking
* {@link ReferenceQueue#remove()} method to take the next
* defunct {@code MonitorEntry} from {@link #sGcQueue} and
* automatically de-registering the finalized monitor
* object.
*/
private static final Thread sGcThread;
// 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);
sGcQueue = new ReferenceQueue<>();
sGcThread =
new Thread("eBux:monitorCleanupThread")
{
@Override
public void run()
{
MonitorEntry entry;
while (true)
{
try
{
entry =
(MonitorEntry) sGcQueue.remove();
sLogger.debug(
"Monitor: de-registering eBus object {}.",
entry.monitorId());
entry.cleanUp();
}
catch (InterruptedException interrupt)
{}
}
}
};
sGcThread.start();
} // end of static.
//-----------------------------------------------------------
// Locals.
//
/**
* Monitor is running on this "host" name. Not required to be
* a network host name.
*/
private final String mHostName;
/**
* Application name.
*/
private final String mAppName;
/**
* Application version.
*/
private final String mAppVersion;
/**
* Application copyright.
*/
@Nullable private final String mAppCopyright;
/**
* Application description.
*/
@Nullable private final String mAppDescription;
/**
* Application-specific attributes.
*/
@Nullable private final EField mAppAttributes;
/**
* Post application info, on-going status, and transient
* status notifications on this subject.
*/
private final String mUpdateSubject;
/**
* Used to synchronize access to static data.
*/
private final Lock mMonitorLock;
/**
* Maps the monitored object identifier to its entry.
*/
private final ConcurrentMap mMonitoredMap;
/**
* Responsible for publishing monitor notification messages.
*/
private MonitorPublisher mPublisher;
/**
* 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 MonitorReplier mReplier;
/**
* Tracks latest application information update message.
*/
private int mAppInfoPosition;
/**
* Tracks latest monitor update message.
*/
private int mUpdatePosition;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Default constructor is private since Monitor may only be
* instantiated by {@link #getMonitor()}.
* @param host monitor running on this "host". Does not
* necessary need to be a network host name.
* @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.
* May be {@code null}.
*/
private Monitor(final String host,
final String name,
final String version,
final @Nullable String copyright,
final @Nullable String description,
final @Nullable EField attributes)
{
mHostName = host;
mAppName = name;
mAppVersion = version;
mAppCopyright = copyright;
mAppDescription = description;
mAppAttributes = attributes;
mUpdateSubject =
String.format(MONITOR_UPDATE_FORMAT, host, name);
mMonitorLock = new ReentrantLock(true);
mMonitoredMap = new ConcurrentHashMap<>();
mAppInfoPosition = 0;
mUpdatePosition = 0;
} // end of Monitor()
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// EObject Interface Implementation.
//
/**
* Returns {@link #MONITOR_NAME}.
* @return returns eBus monitor name.
*/
@Override
public String name()
{
return (MONITOR_NAME);
} // end of name()
/**
* Creates, registers, and starts monitor publisher and
* replier eBus objects.
*/
@Override
public void startup()
{
sLogger.debug("{}: starting up.",
this.name());
mPublisher = new MonitorPublisher();
EFeed.register(mPublisher);
// Instantiate the monitored object request handler and
// advertise it.
mReplier = new MonitorReplier();
EFeed.register(mReplier);
EFeed.startup(ImmutableList.of(mPublisher, mReplier));
} // end of startup()
// Note: eBus monitor singleton instance may not be shut
// down once started.
//
// end of EObject Interface Implementation.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get Methods.
//
/**
* Returns configured host name. Please note that this name
* is not necessarily a network host name.
* @return host name.
*/
public String hostName()
{
return (mHostName);
} // end of hostName()
/**
* Returns configured application name.
* @return application name.
*/
public String applicationName()
{
return (mAppName);
} // end of applicationName()
/**
* Returns configured application version.
* @return application version.
*/
public String version()
{
return (mAppVersion);
} // end of version()
/**
* Returns application copyright. May return a {@code null}
* value.
* @return application copyright.
*/
@Nullable public String copyright()
{
return (mAppCopyright);
} // end of copyright()
/**
* Returns application description. May return a {@code null}
* value.
* @return application description.
*/
@Nullable public String description()
{
return (mAppDescription);
} // end of description()
/**
* Returns application attributes. May return a {@code null}
* value.
* @return application attributes.
*/
@Nullable public EField attributes()
{
return (mAppAttributes);
} // end of attributes()
/**
* Returns singleton monitor instance. Returns {@code null}
* if monitor was not previously
* {@link #openMonitor(String, String, String, String, String, EField) opened}.
* @return singleton monitor instance.
*/
public static @Nullable Monitor getMonitor()
{
return (sMonitor);
} // end of getMonitor()
//
// end of Get Methods.
//-----------------------------------------------------------
/**
* Registers a monitorable object and returns unique monitor
* identifier associated with registered object. The object's
* initial persistent status is set to
* {@link ActionLevel#NO_ACTION}, "Registered".
* An eBus object must successfully register prior to
* {@link #update(ActionLevel, String, String, EObject) updating}
* its status or posting a
* {@link #transientStatus(ActionLevel, String, String, EObject) transient}
* event.
*
* Note: monitor does not maintain
* a strong references to {@code obj}. If this eBus object is
* finalized while still registered with {@code Monitor},
* then monitor is informed of this finalization and
* automatically de-registers the finalized eBus object.
*
* @param obj register this monitorable object.
* @throws NullPointerException
* if {@code obj} is {@code null}.
* @throws IllegalArgumentException
* if {@code obj} is {@code null} or
* {@link EObject#name} returns a {@code null} or empty
* string.
*
* @see #update(ActionLevel, String, String, EObject)
* @see #transientStatus(ActionLevel, String, String, EObject)
* @see #deregister(EObject)
*/
public void register(final EObject obj)
{
final String instanceName =
(Objects.requireNonNull(obj, "obj is null")).name();
if (Strings.isNullOrEmpty(instanceName))
{
throw (
new IllegalArgumentException(
"instance name is either null or empty"));
}
MonitorEntry entry = findEntry(obj);
// Is this eBus object already registered?
if (entry == null)
{
final String typeName = (obj.getClass()).getName();
final MonitorId monitorId =
MonitorId.builder()
.typeName(typeName)
.instanceName(instanceName)
.id(nextMonitorId(obj))
.build();
// No. Continue with the registration.
entry = new MonitorEntry(obj, monitorId);
// Is this object already registered?
sLogger.debug(
"Registering {} for monitoring.", monitorId);
mMonitoredMap.put(monitorId, entry);
// Tell the world about this new monitored object.
mPublisher.publish(
MonitorUpdate.builder()
.subject(mUpdateSubject)
.publisherId(MONITOR_PUBLISHER_ID)
.position(mUpdatePosition)
.hostName(mHostName)
.appName(mAppName)
.instance(monitorId)
.updateType(UpdateType.REGISTER)
.actionLevel(ActionLevel.NO_ACTION)
.actionName(REGISTER_ACTION_NAME)
.build());
++mUpdatePosition;
}
// Yes, ignore this duplicate registration.
} // end of register(EObject)
/**
* 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. May be
* {@code null} or an empty string.
* @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, EObject)
* @see #register(EObject)
* @see #deregister(EObject)
*/
public void update(final ActionLevel actionLevel,
final String actionName,
final @Nullable String actionMsg,
final EObject obj)
{
Objects.requireNonNull(obj, "obj is null");
if (Strings.isNullOrEmpty(actionName))
{
throw (
new IllegalArgumentException(
"null or empty actionName"));
}
final MonitorEntry entry = findEntry(obj);
// The actionMsg may be null or empty.
// But the monitored object must be registered.
if (entry == null)
{
throw (
new IllegalStateException("obj not registered"));
}
sLogger.debug(
"Monitor: {} update action level={}, name={}, message={}",
obj.name(),
actionLevel,
actionName,
actionMsg);
entry.updatePersistent(actionLevel,
actionName,
actionMsg);
mPublisher.publish(
MonitorUpdate.builder()
.subject(mUpdateSubject)
.publisherId(MONITOR_PUBLISHER_ID)
.position(mUpdatePosition)
.hostName(mHostName)
.appName(mAppName)
.instance(entry.monitorId())
.updateType(UpdateType.ON_GOING_UPDATE)
.actionLevel(actionLevel)
.actionName(actionName)
.actionMessage(actionMsg)
.build());
++mUpdatePosition;
} // end of updatePersistent(ActionLevel, String, String, EObject)
/**
* 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. May be
* {@code null} or an empty string.
* @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(EObject)
* @see #update(ActionLevel, String, String, EObject)
* @see #deregister(EObject)
*/
public void transientStatus(final ActionLevel actionLevel,
final String actionName,
final @Nullable String actionMsg,
final EObject obj)
{
Objects.requireNonNull(obj, "null obj");
if (Strings.isNullOrEmpty(actionName))
{
throw (
new IllegalArgumentException(
"null or empty actionName"));
}
final MonitorEntry entry = findEntry(obj);
// The actionMsg may be null or empty.
// But the monitored object must be registered.
if (entry == null)
{
throw (
new IllegalStateException("obj not registered"));
}
sLogger.debug(
"Monitor: {} transient action level={}, name={}, message={}",
obj.name(),
actionLevel,
actionName,
actionMsg);
entry.updateTransient(actionLevel,
actionName,
actionMsg);
mPublisher.publish(
MonitorUpdate.builder()
.subject(mUpdateSubject)
.publisherId(MONITOR_PUBLISHER_ID)
.position(mUpdatePosition)
.hostName(mHostName)
.appName(mAppName)
.instance(entry.monitorId())
.updateType(UpdateType.TRANSIENT_UPDATE)
.actionLevel(actionLevel)
.actionName(actionName)
.actionMessage(actionMsg)
.build());
++mUpdatePosition;
} // end of transientStatus(...)
/**
* Removes a registered eBus 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, EObject)
* @see #transientStatus(ActionLevel, String, String, EObject)
* @see #register(EObject)
*/
public void deregister(final EObject obj)
{
Objects.requireNonNull(obj, "null obj");
final MonitorEntry entry = findEntry(obj);
// Is this monitored object currently registered?
if (entry != null)
{
// Yes. Continue with the deregistration.
deregister(entry);
}
// Monitored object is not registered. Ignore this
// request.
} // end of deregister(EObject)
/**
* Creates, starts, and returns singleton monitor instance.
* Please note that when calling this method for the first
* time, the method blocks until {@code Monitor} instance
* is started. Parameter values are left up to the user to
* decide what constitutes a valid setting. Note that these
* values should be meaningful to both the application and
* to those monitoring the application.
*
* For example, consider {@code host}. A natural reaction is
* to use the name configured by the network support team.
* But these names are often arcane and meaningful only to
* network support. The application team may refer to this
* host as "uat-ny-1" - meaning the first user acceptance
* testing host (uat) site in New York. Since
* {@code Monitor} operates at the application level, this
* would be a better host name. This also applies to the
* remaining parameters.
*
*
* The {@code attributes} parameter allows an application
* to add any number of additional settings by extending an
* eBus {@link EField}. A partial example is:
*
* import net.sf.eBus.messages.EField;
public final class MyAppAttributes
extends EField {
// Report when this application was released to production.
public final ZonedDateTime releaseDate;
// Text providing production release authorization.
public final String releaseSignOff;
// Application is running on this Java version.
public final String javaVersion;
// JVM implementation vendor.
public final String javaVendor;
// ... and any other attributes deemed necessary for
// application support.
}
*
* If monitor is already open, then returns the monitor
* singleton instance.
*
* @param host monitor running on this "host". Does not
* necessary need to be a network host name.
* @param appName 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.
* May be {@code null}.
* @return singleton monitor instance.
* @throws IllegalArgumentException
* if either {@code host}, {@code name} or {@code version} is
* {@code null} or empty.
*/
public static Monitor openMonitor(final String host,
final String appName,
final String version,
@Nullable final String copyright,
@Nullable final String description,
@Nullable final EField attributes)
{
if (Strings.isNullOrEmpty(host))
{
throw (
new IllegalArgumentException(
"host is either null or empty"));
}
if (Strings.isNullOrEmpty(appName))
{
throw (
new IllegalArgumentException(
"appName is either null or empty"));
}
if (Strings.isNullOrEmpty(version))
{
throw (
new IllegalArgumentException(
"version is either null or empty"));
}
sInstanceLock.lock();
try
{
if (sMonitor == null)
{
sLogger.info("Opening eBus monitor.");
sMonitor = new Monitor(host,
appName,
version,
copyright,
description,
attributes);
EFeed.register(sMonitor);
EFeed.startup(sMonitor);
}
}
finally
{
sInstanceLock.unlock();
}
return (sMonitor);
} // end of openMonitor(...)
/**
* Performs the actual work of de-registering a monitored
* object.
* @param entry monitored eBus object's entry.
*/
private void deregister(final MonitorEntry entry)
{
final MonitorId monitorId = entry.monitorId();
sLogger.debug("Deregistering {} from monitoring.",
monitorId);
mMonitoredMap.remove(monitorId);
// Publish and close the monitor entry.
entry.updatePersistent(
ActionLevel.NO_ACTION,
"Deregistered",
"Deregistered from monitor subsystem");
// Tell the world about this change.
mPublisher.publish(
MonitorUpdate.builder()
.instance(monitorId)
.subject(mUpdateSubject)
.publisherId(MONITOR_PUBLISHER_ID)
.position(mUpdatePosition)
.hostName(mHostName)
.appName(mAppName)
.instance(monitorId)
.updateType(UpdateType.DEREGISTER)
.actionLevel(ActionLevel.NO_ACTION)
.actionName(DEREGISTER_ACTION_NAME)
.build());
++mUpdatePosition;
} // end of deregister(MonitorEntry)
/**
* Returns monitor entry for the given object. Returns
* {@code null} if eBus object is not registered for
* monitoring.
* @param obj eBus object.
* @return eBus object's registered entry.
*/
@Nullable private MonitorEntry findEntry(final EObject obj)
{
final Iterator mIt =
(mMonitoredMap.values()).iterator();
MonitorEntry retval = null;
while (retval == null && mIt.hasNext())
{
retval = mIt.next();
// Is this entry for the given eBus object?
// (i.e. does the entry reference obj)
if (retval.get() != obj)
{
// No. Move on to the next entry.
retval = null;
}
}
return (retval);
} // end of findEntry(EObject)
/**
* Returns a monitor identifier for given monitorable object.
* If given object is an {@link EObject eBus object}, then
* uses the eBus client identifier. Otherwise assigns next
* value from the monitor identifier pool.
* @param obj find identifier for this monitorable object.
* @return monitor identifier.
*/
private static int nextMonitorId(final EObject obj)
{
final EClient client =
EClient.findOrCreateClient(
obj, ClientLocation.LOCAL);
return (client.clientId());
} // end of nextMonitorId(EObject)
//---------------------------------------------------------------
// Inner classes.
//
/**
* This passive, immutable class is used to encapsulate a
* registered eBus object, its {@link MonitorId} and latest
* {@code PersistentStatus} message.
*/
private final class MonitorEntry
extends WeakReference
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* The monitored object. Hold the reference to prevent
* its disappearing.
*/
private final EObject mObject;
/**
* The monitored object's identifier.
*/
private final MonitorId mId;
/**
* 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;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates an entry for the given monitorable object and
* identifier.
* @param object monitored object.
* @param id monitor identifier.
*/
public MonitorEntry(final EObject object,
final MonitorId id)
{
super (object, sGcQueue);
mObject = object;
mId = id;
mPersistLevel = ActionLevel.NO_ACTION;
mPersistName = "Registered";
mPersistMessage =
"Registered with monitor subsystem";
} // end of MonitorEntry(EObject, MonitorId)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get methods.
//
/**
* Returns monitored object.
* @return monitored object.
*/
public EObject monitoredObject()
{
return (mObject);
} // end of monitoredObject()
/**
* Returns monitored object identifier.
* @return monitor identifier.
*/
public MonitorId monitorId()
{
return (mId);
} // end of monitorId()
//
// end of Get methods.
//-------------------------------------------------------
/**
* 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)
{
sLogger.trace(
"MonitorEntry {} update: level={}, action={}, message={}.",
mId.instanceName,
level,
actionName,
actionMsg);
mPersistLevel = level;
mPersistName = actionName;
mPersistMessage = actionMsg;
} // 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)
{
sLogger.trace(
"MonitorEntry {} transient: level={}, action={}, message={}.",
mId.instanceName,
level,
actionName,
actionMsg);
} // end of updateTransient(ActionLevel, String, String)
/**
* De-registers this finalized eBus object.
*/
private void cleanUp()
{
// Clean up means de-register this object.
deregister(this);
} // end of cleanUp()
/**
* Returns a persistent status message based on the
* monitor's configuration.
* @return a new persistent status message.
*/
private PersistentStatusMessage generatePersistentMessage()
{
final PersistentStatusMessage.Builder builder =
PersistentStatusMessage.builder();
return (builder.subject(mId.toString())
.hostName(mHostName)
.appName(mAppName)
.instance(mId)
.actionLevel(mPersistLevel)
.actionName(mPersistName)
.actionMessage(mPersistMessage)
.build());
} // end of generatePersistentMessage()
} // end of class MonitorEntry
/**
* The singleton application-level publisher for the JVM.
* Publishes the application information and monitor update
* messages.
*/
private 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;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new monitor notification publisher.
*/
private MonitorPublisher()
{}
//
// 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, mUpdateSubject);
final EMessageKey updateKey =
new EMessageKey(MonitorUpdate.class, mUpdateSubject);
// 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();
sLogger.debug("{}: advertising {} and {} feeds.",
this.name(),
appInfoKey,
updateKey);
mAppInfoFeed.advertise();
mUpdateFeed.advertise();
mAppInfoFeed.updateFeedState(EFeedState.UP);
mUpdateFeed.updateFeedState(EFeedState.UP);
} // 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)
{
sLogger.trace(
"MonitorPublisher: {} publish status is {}.",
feed.key(),
feedState);
// Is this the application information feed?
// Did the feed switch from down to up?
if (feed == mAppInfoFeed &&
feedState == EFeedState.UP)
{
// Yes to both. Publish the application
// information to the first subscriber.
publish(
ApplicationInfo.builder()
.subject(mUpdateSubject)
.publisherId(MONITOR_PUBLISHER_ID)
.position(mAppInfoPosition)
.hostName(mHostName)
.appName(mAppName)
.appVersion(mAppVersion)
.copyright(mAppCopyright)
.description(mAppDescription)
.attributes(mAppAttributes)
.build());
++mAppInfoPosition;
}
} // end of publishStatus(EFeedState, IEPublishFeed)
//
// 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 != null && 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 != null && mUpdateFeed.isFeedUp())
{
mUpdateFeed.publish(msg);
}
} // end of publish(MonitorUpdate)
} // end of class MonitorPublisher
/**
* This class replies to {@link MonitoredObjectRequest}
* messages.
*/
private final class MonitorReplier
implements EReplier
{
//-----------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a monitor request handler instance.
*/
private MonitorReplier()
{}
//
// 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 appInfoKey =
new EMessageKey(ApplicationInfoRequest.class,
APP_INFO_REQUEST_SUBJECT);
final EMessageKey requestKey =
new EMessageKey(MonitoredObjectRequest.class,
ONGOING_REQUEST_SUBJECT);
final EReplyFeed appInfoFeed =
(EReplyFeed.builder())
.target(this)
.messageKey(appInfoKey)
.scope(FeedScope.LOCAL_AND_REMOTE)
.requestCallback(this::onAppInfoRequest)
.build();
final EReplyFeed updateFeed =
(EReplyFeed.builder())
.target(this)
.messageKey(requestKey)
.scope(FeedScope.LOCAL_AND_REMOTE)
.requestCallback(this::onPersistentRequest)
.build();
sLogger.debug("{}: advertising {} feed.",
this.name(),
appInfoKey);
appInfoFeed.advertise();
appInfoFeed.updateFeedState(EFeedState.UP);
sLogger.debug("{}: advertising {} feed.",
this.name(),
requestKey);
updateFeed.advertise();
updateFeed.updateFeedState(EFeedState.UP);
} // end of startup()
// Since this replier is never shutdown, the shutdown()
// method is not overridden.
//
// end of EObject Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// EReplier Interface Implementation.
//
/**
* 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.
//-------------------------------------------------------
//-------------------------------------------------------
// Request Handlers.
//
/**
* Generates an application information reply to this
* request.
* @param request application information request.
*/
private void onAppInfoRequest(final ERequest request)
{
final ApplicationInfoReply.Builder builder =
ApplicationInfoReply.builder();
mMonitorLock.lock();
try
{
builder.subject((request.key()).subject())
.replyStatus(ReplyStatus.OK_FINAL)
.hostName(mHostName)
.appName(mAppName)
.appVersion(mAppVersion)
.copyright(mAppCopyright)
.description(mAppDescription)
.attributes(mAppAttributes);
}
finally
{
mMonitorLock.unlock();
}
request.reply(builder.build());
} // end of onAppInfoRequest(ERequest)
/**
* Generates a monitored object reply to this
* request for all persistent statuses.
* @param request post the reply to this request.
*/
private void onPersistentRequest(final ERequest request)
{
final PersistentStatusMessage[] statuses;
mMonitorLock.lock();
try
{
int i = 0;
statuses =
new PersistentStatusMessage[mMonitoredMap.size()];
for (MonitorEntry entry : mMonitoredMap.values())
{
statuses[i] =
entry.generatePersistentMessage();
++i;
}
}
finally
{
mMonitorLock.unlock();
}
request.reply(
MonitoredObjectReply.builder()
.subject((request.key()).subject())
.replyStatus(ReplyStatus.OK_FINAL)
.hostName(mHostName)
.appName(mAppName)
.statuses(statuses)
.build());
} // end of onPersistentRequest(ERequest)
//
// end of Request Handlers.
//-------------------------------------------------------
} // end of class MonitorReplier
} // end of class Monitor