SlowQueryReportJmx.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.jdbc.pool.interceptor;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.InstanceAlreadyExistsException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.RuntimeOperationsException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorProperty;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import org.apache.tomcat.jdbc.pool.jmx.JmxUtil;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* JMX-enabled slow query report interceptor. Publishes slow and failed query statistics to JMX and provides
* notifications when slow or failed queries are detected.
* <p>
* The MBean and the notification broadcaster are published for the pool <em>name</em>: the first interceptor instance
* to borrow a connection registers them, and every other instance sharing the name - the other connections of that
* pool, the second interceptor of the recommended two-thresholds configuration, or a second pool configured with the
* same name - reports through them. They stay published until the last live pool registered under the name closes;
* closing one of several same-named pools leaves the registration with the survivors. A pool that starts under the
* name after that last close has decided its release - racing it - adopts the registration still published under the
* name, and the pending release notices the takeover and leaves the registration with the new pool.
* </p>
* <p>
* A JMX client's listener subscription belongs to the registration it was added through. JMX cannot carry a
* subscription across an unregistration, so after the last pool of a name has closed, a client must attach its
* listeners again to receive notifications from a pool started under that name anew.
* </p>
*/
public class SlowQueryReportJmx extends SlowQueryReport implements NotificationEmitter, SlowQueryReportJmxMBean {
/**
* Notification type for slow query events.
*/
public static final String SLOW_QUERY_NOTIFICATION = "SLOW QUERY";
/**
* Notification type for failed query events.
*/
public static final String FAILED_QUERY_NOTIFICATION = "FAILED QUERY";
/**
* Property key for overriding the JMX object name.
*/
public static final String objectNameAttribute = "objectName";
/**
* Composite type used for JMX query statistics data.
*/
protected static volatile CompositeType SLOW_QUERY_TYPE;
private static final Log log = LogFactory.getLog(SlowQueryReportJmx.class);
/**
* Creates a new SlowQueryReportJmx instance.
*/
public SlowQueryReportJmx() {
}
/**
* Registry of JMX MBeans for each connection pool.
*/
protected static final ConcurrentHashMap<String, SlowQueryReportJmxMBean> mbeans = new ConcurrentHashMap<>();
/**
* Notification broadcasters for each connection pool. Interceptor instances are created per pooled connection, but
* JMX clients attach their listeners to the single MBean registered per pool, so notifications raised by any
* instance of a pool must reach the broadcaster of the pool rather than a broadcaster private to the instance.
*/
protected static final ConcurrentHashMap<String, NotificationBroadcasterSupport> notifiers = new ConcurrentHashMap<>();
/**
* Registry of the first registration failure seen for each pool name, holding the ObjectName that failed.
* Registration is retried by every borrow of such a pool, but only the first failure of a name is logged with its
* exception, so a persistent refusal - a foreign MBean holding the name, a security restriction - is not reported
* with a stack trace once per borrow. The marker is dropped when a registration under the name succeeds and when
* the name's release runs through {@link #deregisterJmx()}, so a later failure cycle is reported loudly once again.
*/
protected static final ConcurrentHashMap<String, ObjectName> registrationFailuresLogged = new ConcurrentHashMap<>();
/**
* The ObjectName this instance published itself under. {@link #registerJmx()} records it on the instance that
* took the registry slot, before the publish, and clears it again when the MBeanServer refused the MBean. The
* release in {@link #deregisterJmx()} unregisters exactly the name recorded on the registration the registry
* held, whatever class and {@code objectName} property the releasing instance carries - a closing pool delivers a
* freshly instantiated interceptor whose definitions need not match those of the pool that registered.
*/
protected volatile ObjectName registeredUnder;
/**
* Notification broadcaster used before the pool name is known; once the pool name is known, {@link #getNotifier()}
* returns the broadcaster shared by all interceptor instances of that pool.
*/
protected volatile NotificationBroadcasterSupport notifier = new NotificationBroadcasterSupport();
/**
* Returns the notification broadcaster that all interceptor instances of the current pool share, so that
* notifications raised on any connection reach the listeners registered with the pool's MBean.
*
* @return the shared broadcaster for this interceptor's pool
*/
protected NotificationBroadcasterSupport getNotifier() {
String name = poolName;
if (name == null) {
return notifier;
}
return notifiers.computeIfAbsent(name, key -> new NotificationBroadcasterSupport());
}
/**
* Adds a notification listener.
*
* @param listener the listener to add
* @param filter the notification filter
* @param handback the handback object
*
* @throws IllegalArgumentException if the listener is null
*/
@Override
public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
throws IllegalArgumentException {
getNotifier().addNotificationListener(listener, filter, handback);
}
/**
* Returns information about the notifications this MBean can send.
*
* @return an array of MBeanNotificationInfo objects
*/
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
return getNotifier().getNotificationInfo();
}
/**
* Removes a notification listener.
*
* @param listener the listener to remove
*
* @throws ListenerNotFoundException if the listener was not previously added
*/
@Override
public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
getNotifier().removeNotificationListener(listener);
}
/**
* Removes a notification listener with the given filter and handback.
*
* @param listener the listener to remove
* @param filter the notification filter
* @param handback the handback object
*
* @throws ListenerNotFoundException if the listener was not previously added
*/
@Override
public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
throws ListenerNotFoundException {
getNotifier().removeNotificationListener(listener, filter, handback);
}
/**
* The name of the connection pool.
*/
protected String poolName = null;
/**
* Atomic sequence counter for notifications.
*/
protected static final AtomicLong notifySequence = new AtomicLong(0);
/**
* Whether to send notifications to the pool.
*/
protected boolean notifyPool = true;
/**
* The connection pool reference.
*/
protected ConnectionPool pool = null;
/**
* Returns the composite type used for JMX query statistics.
*
* @return the composite type, or {@code null} if initialization failed
*/
protected static CompositeType getCompositeType() {
if (SLOW_QUERY_TYPE == null) {
try {
SLOW_QUERY_TYPE = new CompositeType(SlowQueryReportJmx.class.getName(),
"Composite data type for query statistics", QueryStats.getFieldNames(),
QueryStats.getFieldDescriptions(), QueryStats.getFieldTypes());
} catch (OpenDataException x) {
log.warn(MessageFactory.get("slowQueryReportJmx.unableToInitCompositeType"), x);
}
}
return SLOW_QUERY_TYPE;
}
/**
* Resets this interceptor for a new connection, registering JMX if applicable.
*
* @param parent the connection pool
* @param con the pooled connection
*/
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
super.reset(parent, con);
if (parent != null) {
poolName = parent.getName();
pool = parent;
registerJmx();
}
}
/**
* Called when the connection pool is closed; releases the JMX registration of the pool's name - the MBean and the
* broadcaster its clients' listeners are attached to - only once that pool was the last live pool registered under
* the name. Both belong to the name and are shared by every live pool of that name, so taking them down while a
* survivor is still running would orphan the survivor's monitoring; releasing them on every close but a decided
* last one would unregister a shared registration that other closing or live pools still rely on. Whether this
* close is the last one is decided atomically by {@link SlowQueryReport#releaseStatistics(ConnectionPool)} on the
* way through {@code super.poolClosed}, so concurrent closes of same-named pools cannot all see a survivor and
* leave the name registered for ever.
* <p>
* The decision and the release are separate steps, and a pool that starts under the name between them has taken
* the name - and the registration still published under it - back; {@link #releaseJmxRegistration(ConnectionPool)}
* therefore re-verifies the name before anything is taken down.
* </p>
*
* @param pool the connection pool being closed
*/
@Override
public void poolClosed(ConnectionPool pool) {
this.poolName = pool.getName();
super.poolClosed(pool);
if (lastPoolOfNameClosed) {
// The statistics of the name were freed by this very close,
// so no live pool is registered under it any more - and the
// registered MBean, whose delegate may be an interceptor
// instance of an already closed pool, goes with them, unless
// a pool has taken the name back in the meantime
releaseJmxRegistration(pool);
}
}
/**
* Completes the release of the current pool name whose statistics the given pool has just freed: drops the MBean
* registration with the name's broadcaster and registration-failure marker through {@link #deregisterJmx()} -
* unless a live pool is registered under the name again that does not include the releasing one. A pool that
* started under the name after the release was decided has taken the name over, adopting the registration still
* published under it, and it - not this close - owns the name's JMX state from then on; the last live pool of the
* name is the one to release it.
* <p>
* The statistics are read the plain way here, without a lock, deliberately: a check computed inside a
* {@code ConcurrentHashMap.compute} on the name would be atomic against a start but would also make that start
* wait behind the release, and a release must never block a pool that is coming up. Without the lock the check
* cannot see a start that begins after it has read - the {@link #deregisterJmx()} that follows then still takes
* down only the registration the registry held at its own read, never a fresher one, and only the name's
* broadcaster and failure marker can be hit early by a start landing inside that window; see
* {@link #deregisterJmx()}. The one case the value guard does not spare is a start whose fresh registration
* lands in the slot between the read here and the read in {@link #deregisterJmx()}: that release takes down
* the value it read, leaving the newcomer to republish on its next borrow - a window narrower than the one
* that existed before this guard, measured in the gap between two map reads. The guard likewise proceeds
* when the statistics still hold the very pool that is closing, which only happens when the same pool
* instance is restarted while this close is still in flight; the restarted generation then republishes its
* registration on its first borrow, like any pool whose registration was released.
* </p>
*
* @param releasedPool the pool whose statistics release decided this release of the name
*/
protected void releaseJmxRegistration(ConnectionPool releasedPool) {
PoolStatistics stats = perPoolStats.get(poolName);
if (stats != null && !stats.hasLivePool(releasedPool)) {
// A pool has started under the name since this close released the
// statistics: the name is live again and the new pool owns
// everything registered under it now - the MBean it adopted, the
// broadcaster its clients' listeners are attached to, and the
// bookkeeping. Leave them for the last live pool of the name.
return;
}
deregisterJmx();
}
/**
* Called when the connection pool is started.
*
* @param pool the connection pool being started
*/
@Override
public void poolStarted(ConnectionPool pool) {
this.pool = pool;
super.poolStarted(pool);
this.poolName = pool.getName();
}
/**
* Reports a failed query and sends a JMX notification if logging is enabled.
*
* @param query the SQL query
* @param args the query arguments
* @param name the name of the method used to execute
* @param start the wall clock time the query execution started
* @param delta the time the execution took before it failed, measured with the monotonic clock
* @param t the throwable that caused the failure
*
* @return the formatted query string
*/
@Override
protected String reportFailedQuery(String query, Object[] args, String name, long start, long delta,
Throwable t) {
query = super.reportFailedQuery(query, args, name, start, delta, t);
if (isLogFailed()) {
notifyJmx(query, FAILED_QUERY_NOTIFICATION);
}
return query;
}
/**
* Sends a JMX notification for the given query and notification type.
*
* @param query the SQL query string
* @param type the notification type (slow or failed query)
*/
protected void notifyJmx(String query, String type) {
try {
long sequence = notifySequence.incrementAndGet();
if (isNotifyPool()) {
// Read the pool and its JMX object each only once: close()
// gives the JMX object up concurrently, and a second read can
// observe the null the first one did not
ConnectionPool p = this.pool;
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = p == null ? null : p.getJmxPool();
if (jmx != null) {
jmx.notify(type, query);
}
} else {
NotificationBroadcasterSupport target = getNotifier();
if (target != null) {
Notification notification = new Notification(type, getNotificationSource(), sequence,
System.currentTimeMillis(), query);
target.sendNotification(notification);
}
}
} catch (RuntimeOperationsException e) {
if (log.isDebugEnabled()) {
log.debug("Unable to send failed query notification.", e);
}
}
}
/**
* Returns the source this MBean's notifications are sent with.
* <p>
* {@link Notification} marshals its source, so a listener outside this JVM - one reached through a JMX connector,
* or one that hands the notification on to another process - only receives the notification at all if the source is
* serializable. This MBean is not, and neither is the pool it reports on, so the source is the {@link ObjectName}
* the MBean is registered under, which names the pool and is serializable. Interceptor instances are created per
* pooled connection and only one of them is the registered MBean's resource, so an instance cannot name itself:
* every instance of a pool name sends the very name its registration was published under. An instance whose name
* cannot be formed - a malformed {@code objectName} property or a pool name that cannot be quoted into an
* ObjectName - was never registered at all, so no listener reached through a connector can exist for it; the source
* is then a string of the default name's shape, which still names the pool for the local listeners the broadcaster
* serves.
*
* @return the ObjectName the registration of this interceptor's pool name is published under, or, when that name
* cannot be formed, a string of the default name's shape
*/
protected Object getNotificationSource() {
try {
return getObjectName(getClass(), poolName);
} catch (MalformedObjectNameException x) {
// The name construction getObjectName uses when no override is
// configured - with a malformed name this MBean was never
// registered, so this fallback is only read by local listeners,
// for which it must still name the pool
return ConnectionPool.POOL_JMX_TYPE_PREFIX + getClass().getName() + ",name=" + poolName;
}
}
/**
* Reports a slow query and sends a JMX notification if logging is enabled.
*
* @param query the SQL query
* @param args the query arguments
* @param name the connection pool name
* @param start the start time in milliseconds
* @param delta the elapsed time in milliseconds
*
* @return the formatted query string
*/
@Override
protected String reportSlowQuery(String query, Object[] args, String name, long start, long delta) {
query = super.reportSlowQuery(query, args, name, start, delta);
if (isLogSlow()) {
notifyJmx(query, SLOW_QUERY_NOTIFICATION);
}
return query;
}
/**
* JMX operation - return the names of all the pools
*
* @return - all the names of pools that we have stored data for, which is a view of the JVM-wide statistics
* registry rather than of this pool alone
*/
@Override
public String[] getPoolNames() {
Set<String> keys = perPoolStats.keySet();
return keys.toArray(new String[0]);
}
/**
* JMX operation - return the name of the pool
*
* @return the name of the pool, unique within the JVM, or {@code null} before this instance is bound to a pool
*/
@Override
public String getPoolName() {
return poolName;
}
/**
* Returns whether notifications are sent to the pool.
*
* @return true if notifications are sent to the pool
*/
public boolean isNotifyPool() {
return notifyPool;
}
/**
* Sets whether notifications are sent to the pool. Configuration entry point only, applied to every interceptor
* instance of a pool by {@code setProperties} while its connection chain is built: it is not part of the JMX
* management interface, because writing it through the registered MBean would reach only the one instance that owns
* the registration and leave the pool's other connections routing as before. See {@link SlowQueryReportJmxMBean}.
*
* @param notifyPool true if notifications should be sent to the pool
*/
public void setNotifyPool(boolean notifyPool) {
this.notifyPool = notifyPool;
}
/**
* JMX operation - remove all stats for this connection pool. The statistics are keyed by pool name and shared by
* every live pool configured with that name, so clearing them through one pool's MBean clears them for all of those
* pools. Only the recorded content goes: the statistics map stays registered with the name - every live instance
* holds that very map and keeps accumulating into it.
*/
@Override
public void resetStats() {
ConcurrentHashMap<String, QueryStats> queries = getPoolStats(poolName);
if (queries != null) {
// Clear the map itself rather than emptying it through an
// iterator: the map is shared with every interceptor instance
// of the name, which all hold this same instance
queries.clear();
}
}
/**
* JMX operation - returns all the queries we have collected.
*
* @return - the slow query report as composite data; an empty array when the pool name holds no statistics map -
* none was ever registered under it, or the last live pool of the name has closed and freed it
*/
@Override
public CompositeData[] getSlowQueriesCD() throws OpenDataException {
// Default to the empty array, never null: the descriptor advertises
// the attribute as a CompositeData array, getPoolNames() beside it
// already degrades to an empty String array, and the name's statistics
// map can be gone while a client still reaches this registration -
// reading between the release of the statistics and the unregistration
// in poolClosed, or permanently when the unregistration of the closed
// pool's MBean was refused and JmxUtil swallowed the failure. A client
// that trusts the advertised type must not find null in its hands.
CompositeDataSupport[] result = new CompositeDataSupport[0];
ConcurrentHashMap<String, QueryStats> queries = getPoolStats(poolName);
if (queries != null) {
// Materialise a point-in-time snapshot before sizing the array:
// the map is shared with every interceptor instance of the pool
// name, which keep adding entries on every query and removing
// them on capacity eviction, so sizing from the live map and
// then filling from its weakly consistent entry set could run
// past the end of the array or leave nulls in it
Map<String, QueryStats> snapshot = new HashMap<>(queries);
result = new CompositeDataSupport[snapshot.size()];
int pos = 0;
for (QueryStats qs : snapshot.values()) {
result[pos++] = qs.getCompositeData(getCompositeType());
}
}
return result;
}
/**
* Releases the MBean registration and the shared broadcaster held for the current pool name - even when the pool
* name is still owned by live pools. Normal pool shutdown releases through {@code super.poolClosed} plus
* {@link #lastPoolOfNameClosed} in {@link #poolClosed(ConnectionPool)}, which keeps the registration for any
* surviving same-named pool and skips the release when a new pool has taken the name back
* ({@link #releaseJmxRegistration(ConnectionPool)}).
* <p>
* The registry slot is dropped only while it still holds the registration the registry was read at: a registration
* a pool installing itself behind this release puts into the slot belongs to that pool and keeps its MBean
* published. The unregistration uses the ObjectName that registration recorded when it was published, so it never
* depends on the class or the {@code objectName} property of the instance running this release - which a closing
* pool delivers freshly, and which need not share either with the registration it releases.
* </p>
* <p>
* The broadcaster and the registration-failure marker belong to the name as much as the MBean does, so the release
* drops them even when this pool never held the registry slot: a pool whose every registration attempt was refused
* still reported through the broadcaster, and its throttle marker must not outlive the name and hush the first
* failure of a later failure cycle under the name. Both entries are created lazily - by {@link #getNotifier()} and
* by listeners a client adds through interceptor handles it still holds - so a client subscribing to the released
* name concurrently with this release can have its late subscription re-insert an entry nothing registered under
* the name uses any more; a fresh registration under the name re-uses or replaces it.
* </p>
*/
protected void deregisterJmx() {
try {
SlowQueryReportJmxMBean registered = mbeans.get(poolName);
if (registered != null && mbeans.remove(poolName, registered)) {
JmxUtil.unregisterJmx(registrationObjectName(registered));
}
notifiers.remove(poolName);
registrationFailuresLogged.remove(poolName);
} catch (MalformedObjectNameException | RuntimeOperationsException e) {
log.warn(MessageFactory.get("slowQueryReportJmx.jmxDeregistrationFailed"), e);
}
}
/**
* Returns the ObjectName the given registry registration published itself under, as the registry owner recorded it
* when {@link #registerJmx()} took the slot. Unregistering this - rather than a name recomputed from the class and
* properties of the instance running the release - is what keeps the unregistration on the name the MBean actually
* occupies: two pools configured with one name may carry interceptor definitions that compute different
* ObjectNames, and the release run by one of them must not give up on the name the other one's registration was
* published under and leave it registered for ever.
*
* @param registered the registry value this release took out of the registry
*
* @return the ObjectName to unregister
*
* @throws MalformedObjectNameException if a fallback ObjectName cannot be formed
*/
protected ObjectName registrationObjectName(SlowQueryReportJmxMBean registered)
throws MalformedObjectNameException {
if (registered instanceof SlowQueryReportJmx) {
SlowQueryReportJmx owner = (SlowQueryReportJmx) registered;
ObjectName published = owner.registeredUnder;
if (published != null) {
return published;
}
// The owner never got to record its name; fall back to the name
// its own registration would be attempted under - the owner's
// class and properties, not this releasing instance's
return owner.getObjectName(owner.getClass(), poolName);
}
// A value this class never registered - the protected registry is open
// to subclasses; the caller's own name shape is all there is to go by
return getObjectName(getClass(), poolName);
}
/**
* Returns the JMX ObjectName for the given class and pool name.
*
* @param clazz the interceptor class
* @param poolName the name of the connection pool
*
* @return the JMX ObjectName
*
* @throws MalformedObjectNameException if the object name is malformed
*/
public ObjectName getObjectName(Class<?> clazz, String poolName) throws MalformedObjectNameException {
ObjectName oname;
Map<String, InterceptorProperty> properties = getProperties();
if (properties != null && properties.containsKey(objectNameAttribute)) {
oname = new ObjectName(properties.get(objectNameAttribute).getValue());
} else {
oname = new ObjectName(ConnectionPool.POOL_JMX_TYPE_PREFIX + clazz.getName() + ",name=" + poolName);
}
return oname;
}
/**
* Registers this interceptor with JMX.
* <p>
* The registration is attempted under the protection of the per-name registry slot: the slot is taken with
* {@code putIfAbsent} so only one instance per pool name registers, and it is released again when the MBeanServer
* refuses the MBean. A failed publish must not keep the slot, because the slot is the only registration gate: every
* later borrow would find it occupied and skip the registration, so the pool would claim an MBean for its whole
* life that was never published - and would not re-register even after whatever made the MBeanServer refuse it is
* gone. Releasing the slot lets a later borrow retry; {@code JmxUtil.registerJmx} cannot be used for the attempt
* because it swallows every exception and returns a name whatever happened, so the failure of its own call is
* unobservable to the caller. Only the first refusal of a name is logged with its exception, so a persistent
* refusal is not reported once per borrow.
*/
protected void registerJmx() {
try {
// Only if we notify the pool itself
if (isNotifyPool()) {
} else if (getCompositeType() != null) {
ObjectName oname = getObjectName(getClass(), poolName);
if (mbeans.putIfAbsent(poolName, this) == null) {
try {
// Record the name this instance takes the slot for
// before the publish: a release racing this
// registration unregisters the very name being
// published, not one of its own choosing
registeredUnder = oname;
ManagementFactory.getPlatformMBeanServer().registerMBean(this, oname);
// A registration under the name works again - drop
// the marker so a later failure cycle reports loudly
registrationFailuresLogged.remove(poolName);
} catch (Exception x) {
mbeans.remove(poolName, this);
registeredUnder = null;
if (oname.equals(registrationFailuresLogged.putIfAbsent(poolName, oname))) {
// The loud report went out with the first
// refusal of this name; repeats ride along at
// debug, without a stack trace, so a persistent
// refusal stays quiet under borrow load
if (log.isDebugEnabled()) {
log.debug("Jmx registration of [" + oname + "] failed again: " + x);
}
} else {
// First refusal for the name, or the name now
// fails under a different ObjectName: report it
// loudly and remember to throttle the repeats
registrationFailuresLogged.put(poolName, oname);
if (x instanceof InstanceAlreadyExistsException) {
// Retryable: the name may be held by a stale
// or foreign MBean that a later borrow finds
// gone
log.warn(MessageFactory.get("slowQueryReportJmx.jmxRegistrationInUse", oname), x);
} else {
log.error(MessageFactory.get("slowQueryReportJmx.jmxRegistrationFailed", oname), x);
}
}
}
}
} else {
log.warn(MessageFactory.get("slowQueryReportJmx.noJmxSupport",
SlowQueryReport.class.getName()));
}
} catch (MalformedObjectNameException | RuntimeOperationsException e) {
log.error(MessageFactory.get("slowQueryReportJmx.jmxRegistrationFailedNoData"), e);
}
}
/**
* Sets the interceptor properties, including the notifyPool flag.
*
* @param properties the interceptor properties
*/
@Override
public void setProperties(Map<String, InterceptorProperty> properties) {
super.setProperties(properties);
final String threshold = "notifyPool";
InterceptorProperty p1 = properties.get(threshold);
if (p1 != null) {
this.setNotifyPool(Boolean.parseBoolean(p1.getValue()));
}
}
}