feat(jdk8): move files to new folder to avoid resources compiled.
This commit is contained in:
@@ -0,0 +1,852 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MBeanServerDelegate;
|
||||
import javax.management.MBeanServerNotification;
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationBroadcaster;
|
||||
import javax.management.NotificationFilter;
|
||||
import javax.management.NotificationFilterSupport;
|
||||
import javax.management.NotificationListener;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.QueryEval;
|
||||
import javax.management.QueryExp;
|
||||
|
||||
import javax.management.remote.NotificationResult;
|
||||
import javax.management.remote.TargetedNotification;
|
||||
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
|
||||
/** A circular buffer of notifications received from an MBean server. */
|
||||
/*
|
||||
There is one instance of ArrayNotificationBuffer for every
|
||||
MBeanServer object that has an attached ConnectorServer. Then, for
|
||||
every ConnectorServer attached to a given MBeanServer, there is an
|
||||
instance of the inner class ShareBuffer. So for example with two
|
||||
ConnectorServers it looks like this:
|
||||
|
||||
ConnectorServer1 -> ShareBuffer1 -\
|
||||
}-> ArrayNotificationBuffer
|
||||
ConnectorServer2 -> ShareBuffer2 -/ |
|
||||
|
|
||||
v
|
||||
MBeanServer
|
||||
|
||||
The ArrayNotificationBuffer has a circular buffer of
|
||||
NamedNotification objects. Each ConnectorServer defines a
|
||||
notification buffer size, and this size is recorded by the
|
||||
corresponding ShareBuffer. The buffer size of the
|
||||
ArrayNotificationBuffer is the maximum of all of its ShareBuffers.
|
||||
When a ShareBuffer is added or removed, the ArrayNotificationBuffer
|
||||
size is adjusted accordingly.
|
||||
|
||||
An ArrayNotificationBuffer also has a BufferListener (which is a
|
||||
NotificationListener) registered on every NotificationBroadcaster
|
||||
MBean in the MBeanServer to which it is attached. The cost of this
|
||||
potentially large set of listeners is the principal motivation for
|
||||
sharing the ArrayNotificationBuffer between ConnectorServers, and
|
||||
also the reason that we are careful to discard the
|
||||
ArrayNotificationBuffer (and its BufferListeners) when there are no
|
||||
longer any ConnectorServers using it.
|
||||
|
||||
The synchronization of this class is inherently complex. In an attempt
|
||||
to limit the complexity, we use just two locks:
|
||||
|
||||
- globalLock controls access to the mapping between an MBeanServer
|
||||
and its ArrayNotificationBuffer and to the set of ShareBuffers for
|
||||
each ArrayNotificationBuffer.
|
||||
|
||||
- the instance lock of each ArrayNotificationBuffer controls access
|
||||
to the array of notifications, including its size, and to the
|
||||
dispose flag of the ArrayNotificationBuffer. The wait/notify
|
||||
mechanism is used to indicate changes to the array.
|
||||
|
||||
If both locks are held at the same time, the globalLock must be
|
||||
taken first.
|
||||
|
||||
Since adding or removing a BufferListener to an MBean can involve
|
||||
calling user code, we are careful not to hold any locks while it is
|
||||
done.
|
||||
*/
|
||||
public class ArrayNotificationBuffer implements NotificationBuffer {
|
||||
private boolean disposed = false;
|
||||
|
||||
// FACTORY STUFF, INCLUDING SHARING
|
||||
|
||||
private static final Object globalLock = new Object();
|
||||
private static final
|
||||
HashMap<MBeanServer,ArrayNotificationBuffer> mbsToBuffer =
|
||||
new HashMap<MBeanServer,ArrayNotificationBuffer>(1);
|
||||
private final Collection<ShareBuffer> sharers = new HashSet<ShareBuffer>(1);
|
||||
|
||||
public static NotificationBuffer getNotificationBuffer(
|
||||
MBeanServer mbs, Map<String, ?> env) {
|
||||
|
||||
if (env == null)
|
||||
env = Collections.emptyMap();
|
||||
|
||||
//Find out queue size
|
||||
int queueSize = EnvHelp.getNotifBufferSize(env);
|
||||
|
||||
ArrayNotificationBuffer buf;
|
||||
boolean create;
|
||||
NotificationBuffer sharer;
|
||||
synchronized (globalLock) {
|
||||
buf = mbsToBuffer.get(mbs);
|
||||
create = (buf == null);
|
||||
if (create) {
|
||||
buf = new ArrayNotificationBuffer(mbs, queueSize);
|
||||
mbsToBuffer.put(mbs, buf);
|
||||
}
|
||||
sharer = buf.new ShareBuffer(queueSize);
|
||||
}
|
||||
/* We avoid holding any locks while calling createListeners.
|
||||
* This prevents possible deadlocks involving user code, but
|
||||
* does mean that a second ConnectorServer created and started
|
||||
* in this window will return before all the listeners are ready,
|
||||
* which could lead to surprising behaviour. The alternative
|
||||
* would be to block the second ConnectorServer until the first
|
||||
* one has finished adding all the listeners, but that would then
|
||||
* be subject to deadlock.
|
||||
*/
|
||||
if (create)
|
||||
buf.createListeners();
|
||||
return sharer;
|
||||
}
|
||||
|
||||
/* Ensure that this buffer is no longer the one that will be returned by
|
||||
* getNotificationBuffer. This method is idempotent - calling it more
|
||||
* than once has no effect beyond that of calling it once.
|
||||
*/
|
||||
static void removeNotificationBuffer(MBeanServer mbs) {
|
||||
synchronized (globalLock) {
|
||||
mbsToBuffer.remove(mbs);
|
||||
}
|
||||
}
|
||||
|
||||
void addSharer(ShareBuffer sharer) {
|
||||
synchronized (globalLock) {
|
||||
synchronized (this) {
|
||||
if (sharer.getSize() > queueSize)
|
||||
resize(sharer.getSize());
|
||||
}
|
||||
sharers.add(sharer);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSharer(ShareBuffer sharer) {
|
||||
boolean empty;
|
||||
synchronized (globalLock) {
|
||||
sharers.remove(sharer);
|
||||
empty = sharers.isEmpty();
|
||||
if (empty)
|
||||
removeNotificationBuffer(mBeanServer);
|
||||
else {
|
||||
int max = 0;
|
||||
for (ShareBuffer buf : sharers) {
|
||||
int bufsize = buf.getSize();
|
||||
if (bufsize > max)
|
||||
max = bufsize;
|
||||
}
|
||||
if (max < queueSize)
|
||||
resize(max);
|
||||
}
|
||||
}
|
||||
if (empty) {
|
||||
synchronized (this) {
|
||||
disposed = true;
|
||||
// Notify potential waiting fetchNotification call
|
||||
notifyAll();
|
||||
}
|
||||
destroyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void resize(int newSize) {
|
||||
if (newSize == queueSize)
|
||||
return;
|
||||
while (queue.size() > newSize)
|
||||
dropNotification();
|
||||
queue.resize(newSize);
|
||||
queueSize = newSize;
|
||||
}
|
||||
|
||||
private class ShareBuffer implements NotificationBuffer {
|
||||
ShareBuffer(int size) {
|
||||
this.size = size;
|
||||
addSharer(this);
|
||||
}
|
||||
|
||||
public NotificationResult
|
||||
fetchNotifications(NotificationBufferFilter filter,
|
||||
long startSequenceNumber,
|
||||
long timeout,
|
||||
int maxNotifications)
|
||||
throws InterruptedException {
|
||||
NotificationBuffer buf = ArrayNotificationBuffer.this;
|
||||
return buf.fetchNotifications(filter, startSequenceNumber,
|
||||
timeout, maxNotifications);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
ArrayNotificationBuffer.this.removeSharer(this);
|
||||
}
|
||||
|
||||
int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
private final int size;
|
||||
}
|
||||
|
||||
|
||||
// ARRAYNOTIFICATIONBUFFER IMPLEMENTATION
|
||||
|
||||
private ArrayNotificationBuffer(MBeanServer mbs, int queueSize) {
|
||||
if (logger.traceOn())
|
||||
logger.trace("Constructor", "queueSize=" + queueSize);
|
||||
|
||||
if (mbs == null || queueSize < 1)
|
||||
throw new IllegalArgumentException("Bad args");
|
||||
|
||||
this.mBeanServer = mbs;
|
||||
this.queueSize = queueSize;
|
||||
this.queue = new ArrayQueue<NamedNotification>(queueSize);
|
||||
this.earliestSequenceNumber = System.currentTimeMillis();
|
||||
this.nextSequenceNumber = this.earliestSequenceNumber;
|
||||
|
||||
logger.trace("Constructor", "ends");
|
||||
}
|
||||
|
||||
private synchronized boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
// We no longer support calling this method from outside.
|
||||
// The JDK doesn't contain any such calls and users are not
|
||||
// supposed to be accessing this class.
|
||||
public void dispose() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Fetch notifications that match the given listeners.</p>
|
||||
*
|
||||
* <p>The operation only considers notifications with a sequence
|
||||
* number at least <code>startSequenceNumber</code>. It will take
|
||||
* no longer than <code>timeout</code>, and will return no more
|
||||
* than <code>maxNotifications</code> different notifications.</p>
|
||||
*
|
||||
* <p>If there are no notifications matching the criteria, the
|
||||
* operation will block until one arrives, subject to the
|
||||
* timeout.</p>
|
||||
*
|
||||
* @param filter an object that will add notifications to a
|
||||
* {@code List<TargetedNotification>} if they match the current
|
||||
* listeners with their filters.
|
||||
* @param startSequenceNumber the first sequence number to
|
||||
* consider.
|
||||
* @param timeout the maximum time to wait. May be 0 to indicate
|
||||
* not to wait if there are no notifications.
|
||||
* @param maxNotifications the maximum number of notifications to
|
||||
* return. May be 0 to indicate a wait for eligible notifications
|
||||
* that will return a usable <code>nextSequenceNumber</code>. The
|
||||
* {@link TargetedNotification} array in the returned {@link
|
||||
* NotificationResult} may contain more than this number of
|
||||
* elements but will not contain more than this number of
|
||||
* different notifications.
|
||||
*/
|
||||
public NotificationResult
|
||||
fetchNotifications(NotificationBufferFilter filter,
|
||||
long startSequenceNumber,
|
||||
long timeout,
|
||||
int maxNotifications)
|
||||
throws InterruptedException {
|
||||
|
||||
logger.trace("fetchNotifications", "starts");
|
||||
|
||||
if (startSequenceNumber < 0 || isDisposed()) {
|
||||
synchronized(this) {
|
||||
return new NotificationResult(earliestSequenceNumber(),
|
||||
nextSequenceNumber(),
|
||||
new TargetedNotification[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check arg validity
|
||||
if (filter == null
|
||||
|| startSequenceNumber < 0 || timeout < 0
|
||||
|| maxNotifications < 0) {
|
||||
logger.trace("fetchNotifications", "Bad args");
|
||||
throw new IllegalArgumentException("Bad args to fetch");
|
||||
}
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.trace("fetchNotifications",
|
||||
"filter=" + filter + "; startSeq=" +
|
||||
startSequenceNumber + "; timeout=" + timeout +
|
||||
"; max=" + maxNotifications);
|
||||
}
|
||||
|
||||
if (startSequenceNumber > nextSequenceNumber()) {
|
||||
final String msg = "Start sequence number too big: " +
|
||||
startSequenceNumber + " > " + nextSequenceNumber();
|
||||
logger.trace("fetchNotifications", msg);
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
|
||||
/* Determine the end time corresponding to the timeout value.
|
||||
Caller may legitimately supply Long.MAX_VALUE to indicate no
|
||||
timeout. In that case the addition will overflow and produce
|
||||
a negative end time. Set end time to Long.MAX_VALUE in that
|
||||
case. We assume System.currentTimeMillis() is positive. */
|
||||
long endTime = System.currentTimeMillis() + timeout;
|
||||
if (endTime < 0) // overflow
|
||||
endTime = Long.MAX_VALUE;
|
||||
|
||||
if (logger.debugOn())
|
||||
logger.debug("fetchNotifications", "endTime=" + endTime);
|
||||
|
||||
/* We set earliestSeq the first time through the loop. If we
|
||||
set it here, notifications could be dropped before we
|
||||
started examining them, so earliestSeq might not correspond
|
||||
to the earliest notification we examined. */
|
||||
long earliestSeq = -1;
|
||||
long nextSeq = startSequenceNumber;
|
||||
List<TargetedNotification> notifs =
|
||||
new ArrayList<TargetedNotification>();
|
||||
|
||||
/* On exit from this loop, notifs, earliestSeq, and nextSeq must
|
||||
all be correct values for the returned NotificationResult. */
|
||||
while (true) {
|
||||
logger.debug("fetchNotifications", "main loop starts");
|
||||
|
||||
NamedNotification candidate;
|
||||
|
||||
/* Get the next available notification regardless of filters,
|
||||
or wait for one to arrive if there is none. */
|
||||
synchronized (this) {
|
||||
|
||||
/* First time through. The current earliestSequenceNumber
|
||||
is the first one we could have examined. */
|
||||
if (earliestSeq < 0) {
|
||||
earliestSeq = earliestSequenceNumber();
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("fetchNotifications",
|
||||
"earliestSeq=" + earliestSeq);
|
||||
}
|
||||
if (nextSeq < earliestSeq) {
|
||||
nextSeq = earliestSeq;
|
||||
logger.debug("fetchNotifications",
|
||||
"nextSeq=earliestSeq");
|
||||
}
|
||||
} else
|
||||
earliestSeq = earliestSequenceNumber();
|
||||
|
||||
/* If many notifications have been dropped since the
|
||||
last time through, nextSeq could now be earlier
|
||||
than the current earliest. If so, notifications
|
||||
may have been lost and we return now so the caller
|
||||
can see this next time it calls. */
|
||||
if (nextSeq < earliestSeq) {
|
||||
logger.trace("fetchNotifications",
|
||||
"nextSeq=" + nextSeq + " < " + "earliestSeq=" +
|
||||
earliestSeq + " so may have lost notifs");
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextSeq < nextSequenceNumber()) {
|
||||
candidate = notificationAt(nextSeq);
|
||||
// Skip security check if NotificationBufferFilter is not overloaded
|
||||
if (!(filter instanceof ServerNotifForwarder.NotifForwarderBufferFilter)) {
|
||||
try {
|
||||
ServerNotifForwarder.checkMBeanPermission(this.mBeanServer,
|
||||
candidate.getObjectName(),"addNotificationListener");
|
||||
} catch (InstanceNotFoundException | SecurityException e) {
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("fetchNotifications", "candidate: " + candidate + " skipped. exception " + e);
|
||||
}
|
||||
++nextSeq;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("fetchNotifications", "candidate: " +
|
||||
candidate);
|
||||
logger.debug("fetchNotifications", "nextSeq now " +
|
||||
nextSeq);
|
||||
}
|
||||
} else {
|
||||
/* nextSeq is the largest sequence number. If we
|
||||
already got notifications, return them now.
|
||||
Otherwise wait for some to arrive, with
|
||||
timeout. */
|
||||
if (notifs.size() > 0) {
|
||||
logger.debug("fetchNotifications",
|
||||
"no more notifs but have some so don't wait");
|
||||
break;
|
||||
}
|
||||
long toWait = endTime - System.currentTimeMillis();
|
||||
if (toWait <= 0) {
|
||||
logger.debug("fetchNotifications", "timeout");
|
||||
break;
|
||||
}
|
||||
|
||||
/* dispose called */
|
||||
if (isDisposed()) {
|
||||
if (logger.debugOn())
|
||||
logger.debug("fetchNotifications",
|
||||
"dispose callled, no wait");
|
||||
return new NotificationResult(earliestSequenceNumber(),
|
||||
nextSequenceNumber(),
|
||||
new TargetedNotification[0]);
|
||||
}
|
||||
|
||||
if (logger.debugOn())
|
||||
logger.debug("fetchNotifications",
|
||||
"wait(" + toWait + ")");
|
||||
wait(toWait);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* We have a candidate notification. See if it matches
|
||||
our filters. We do this outside the synchronized block
|
||||
so we don't hold up everyone accessing the buffer
|
||||
(including notification senders) while we evaluate
|
||||
potentially slow filters. */
|
||||
ObjectName name = candidate.getObjectName();
|
||||
Notification notif = candidate.getNotification();
|
||||
List<TargetedNotification> matchedNotifs =
|
||||
new ArrayList<TargetedNotification>();
|
||||
logger.debug("fetchNotifications",
|
||||
"applying filter to candidate");
|
||||
filter.apply(matchedNotifs, name, notif);
|
||||
|
||||
if (matchedNotifs.size() > 0) {
|
||||
/* We only check the max size now, so that our
|
||||
returned nextSeq is as large as possible. This
|
||||
prevents the caller from thinking it missed
|
||||
interesting notifications when in fact we knew they
|
||||
weren't. */
|
||||
if (maxNotifications <= 0) {
|
||||
logger.debug("fetchNotifications",
|
||||
"reached maxNotifications");
|
||||
break;
|
||||
}
|
||||
--maxNotifications;
|
||||
if (logger.debugOn())
|
||||
logger.debug("fetchNotifications", "add: " +
|
||||
matchedNotifs);
|
||||
notifs.addAll(matchedNotifs);
|
||||
}
|
||||
|
||||
++nextSeq;
|
||||
} // end while
|
||||
|
||||
/* Construct and return the result. */
|
||||
int nnotifs = notifs.size();
|
||||
TargetedNotification[] resultNotifs =
|
||||
new TargetedNotification[nnotifs];
|
||||
notifs.toArray(resultNotifs);
|
||||
NotificationResult nr =
|
||||
new NotificationResult(earliestSeq, nextSeq, resultNotifs);
|
||||
if (logger.debugOn())
|
||||
logger.debug("fetchNotifications", nr.toString());
|
||||
logger.trace("fetchNotifications", "ends");
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
synchronized long earliestSequenceNumber() {
|
||||
return earliestSequenceNumber;
|
||||
}
|
||||
|
||||
synchronized long nextSequenceNumber() {
|
||||
return nextSequenceNumber;
|
||||
}
|
||||
|
||||
synchronized void addNotification(NamedNotification notif) {
|
||||
if (logger.traceOn())
|
||||
logger.trace("addNotification", notif.toString());
|
||||
|
||||
while (queue.size() >= queueSize) {
|
||||
dropNotification();
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("addNotification",
|
||||
"dropped oldest notif, earliestSeq=" +
|
||||
earliestSequenceNumber);
|
||||
}
|
||||
}
|
||||
queue.add(notif);
|
||||
nextSequenceNumber++;
|
||||
if (logger.debugOn())
|
||||
logger.debug("addNotification", "nextSeq=" + nextSequenceNumber);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private void dropNotification() {
|
||||
queue.remove(0);
|
||||
earliestSequenceNumber++;
|
||||
}
|
||||
|
||||
synchronized NamedNotification notificationAt(long seqNo) {
|
||||
long index = seqNo - earliestSequenceNumber;
|
||||
if (index < 0 || index > Integer.MAX_VALUE) {
|
||||
final String msg = "Bad sequence number: " + seqNo + " (earliest "
|
||||
+ earliestSequenceNumber + ")";
|
||||
logger.trace("notificationAt", msg);
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
return queue.get((int) index);
|
||||
}
|
||||
|
||||
private static class NamedNotification {
|
||||
NamedNotification(ObjectName sender, Notification notif) {
|
||||
this.sender = sender;
|
||||
this.notification = notif;
|
||||
}
|
||||
|
||||
ObjectName getObjectName() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
Notification getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "NamedNotification(" + sender + ", " + notification + ")";
|
||||
}
|
||||
|
||||
private final ObjectName sender;
|
||||
private final Notification notification;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add our listener to every NotificationBroadcaster MBean
|
||||
* currently in the MBean server and to every
|
||||
* NotificationBroadcaster later created.
|
||||
*
|
||||
* It would be really nice if we could just do
|
||||
* mbs.addNotificationListener(new ObjectName("*:*"), ...);
|
||||
* Definitely something for the next version of JMX.
|
||||
*
|
||||
* There is a nasty race condition that we must handle. We
|
||||
* first register for MBean-creation notifications so we can add
|
||||
* listeners to new MBeans, then we query the existing MBeans to
|
||||
* add listeners to them. The problem is that a new MBean could
|
||||
* arrive after we register for creations but before the query has
|
||||
* completed. Then we could see the MBean both in the query and
|
||||
* in an MBean-creation notification, and we would end up
|
||||
* registering our listener twice.
|
||||
*
|
||||
* To solve this problem, we arrange for new MBeans that arrive
|
||||
* while the query is being done to be added to the Set createdDuringQuery
|
||||
* and we do not add a listener immediately. When the query is done,
|
||||
* we atomically turn off the addition of new names to createdDuringQuery
|
||||
* and add all the names that were there to the result of the query.
|
||||
* Since we are dealing with Sets, the result is the same whether or not
|
||||
* the newly-created MBean was included in the query result.
|
||||
*
|
||||
* It is important not to hold any locks during the operation of adding
|
||||
* listeners to MBeans. An MBean's addNotificationListener can be
|
||||
* arbitrary user code, and this could deadlock with any locks we hold
|
||||
* (see bug 6239400). The corollary is that we must not do any operations
|
||||
* in this method or the methods it calls that require locks.
|
||||
*/
|
||||
private void createListeners() {
|
||||
logger.debug("createListeners", "starts");
|
||||
|
||||
synchronized (this) {
|
||||
createdDuringQuery = new HashSet<ObjectName>();
|
||||
}
|
||||
|
||||
try {
|
||||
addNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
|
||||
creationListener, creationFilter, null);
|
||||
logger.debug("createListeners", "added creationListener");
|
||||
} catch (Exception e) {
|
||||
final String msg = "Can't add listener to MBean server delegate: ";
|
||||
RuntimeException re = new IllegalArgumentException(msg + e);
|
||||
EnvHelp.initCause(re, e);
|
||||
logger.fine("createListeners", msg + e);
|
||||
logger.debug("createListeners", e);
|
||||
throw re;
|
||||
}
|
||||
|
||||
/* Spec doesn't say whether Set returned by QueryNames can be modified
|
||||
so we clone it. */
|
||||
Set<ObjectName> names = queryNames(null, broadcasterQuery);
|
||||
names = new HashSet<ObjectName>(names);
|
||||
|
||||
synchronized (this) {
|
||||
names.addAll(createdDuringQuery);
|
||||
createdDuringQuery = null;
|
||||
}
|
||||
|
||||
for (ObjectName name : names)
|
||||
addBufferListener(name);
|
||||
logger.debug("createListeners", "ends");
|
||||
}
|
||||
|
||||
private void addBufferListener(ObjectName name) {
|
||||
checkNoLocks();
|
||||
if (logger.debugOn())
|
||||
logger.debug("addBufferListener", name.toString());
|
||||
try {
|
||||
addNotificationListener(name, bufferListener, null, name);
|
||||
} catch (Exception e) {
|
||||
logger.trace("addBufferListener", e);
|
||||
/* This can happen if the MBean was unregistered just
|
||||
after the query. Or user NotificationBroadcaster might
|
||||
throw unexpected exception. */
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBufferListener(ObjectName name) {
|
||||
checkNoLocks();
|
||||
if (logger.debugOn())
|
||||
logger.debug("removeBufferListener", name.toString());
|
||||
try {
|
||||
removeNotificationListener(name, bufferListener);
|
||||
} catch (Exception e) {
|
||||
logger.trace("removeBufferListener", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNotificationListener(final ObjectName name,
|
||||
final NotificationListener listener,
|
||||
final NotificationFilter filter,
|
||||
final Object handback)
|
||||
throws Exception {
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
|
||||
public Void run() throws InstanceNotFoundException {
|
||||
mBeanServer.addNotificationListener(name,
|
||||
listener,
|
||||
filter,
|
||||
handback);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw extractException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNotificationListener(final ObjectName name,
|
||||
final NotificationListener listener)
|
||||
throws Exception {
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
|
||||
public Void run() throws Exception {
|
||||
mBeanServer.removeNotificationListener(name, listener);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw extractException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ObjectName> queryNames(final ObjectName name,
|
||||
final QueryExp query) {
|
||||
PrivilegedAction<Set<ObjectName>> act =
|
||||
new PrivilegedAction<Set<ObjectName>>() {
|
||||
public Set<ObjectName> run() {
|
||||
return mBeanServer.queryNames(name, query);
|
||||
}
|
||||
};
|
||||
try {
|
||||
return AccessController.doPrivileged(act);
|
||||
} catch (RuntimeException e) {
|
||||
logger.fine("queryNames", "Failed to query names: " + e);
|
||||
logger.debug("queryNames", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInstanceOf(final MBeanServer mbs,
|
||||
final ObjectName name,
|
||||
final String className) {
|
||||
PrivilegedExceptionAction<Boolean> act =
|
||||
new PrivilegedExceptionAction<Boolean>() {
|
||||
public Boolean run() throws InstanceNotFoundException {
|
||||
return mbs.isInstanceOf(name, className);
|
||||
}
|
||||
};
|
||||
try {
|
||||
return AccessController.doPrivileged(act);
|
||||
} catch (Exception e) {
|
||||
logger.fine("isInstanceOf", "failed: " + e);
|
||||
logger.debug("isInstanceOf", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* This method must not be synchronized. See the comment on the
|
||||
* createListeners method.
|
||||
*
|
||||
* The notification could arrive after our buffer has been destroyed
|
||||
* or even during its destruction. So we always add our listener
|
||||
* (without synchronization), then we check if the buffer has been
|
||||
* destroyed and if so remove the listener we just added.
|
||||
*/
|
||||
private void createdNotification(MBeanServerNotification n) {
|
||||
final String shouldEqual =
|
||||
MBeanServerNotification.REGISTRATION_NOTIFICATION;
|
||||
if (!n.getType().equals(shouldEqual)) {
|
||||
logger.warning("createNotification", "bad type: " + n.getType());
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectName name = n.getMBeanName();
|
||||
if (logger.debugOn())
|
||||
logger.debug("createdNotification", "for: " + name);
|
||||
|
||||
synchronized (this) {
|
||||
if (createdDuringQuery != null) {
|
||||
createdDuringQuery.add(name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInstanceOf(mBeanServer, name, broadcasterClass)) {
|
||||
addBufferListener(name);
|
||||
if (isDisposed())
|
||||
removeBufferListener(name);
|
||||
}
|
||||
}
|
||||
|
||||
private class BufferListener implements NotificationListener {
|
||||
public void handleNotification(Notification notif, Object handback) {
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("BufferListener.handleNotification",
|
||||
"notif=" + notif + "; handback=" + handback);
|
||||
}
|
||||
ObjectName name = (ObjectName) handback;
|
||||
addNotification(new NamedNotification(name, notif));
|
||||
}
|
||||
}
|
||||
|
||||
private final NotificationListener bufferListener = new BufferListener();
|
||||
|
||||
private static class BroadcasterQuery
|
||||
extends QueryEval implements QueryExp {
|
||||
private static final long serialVersionUID = 7378487660587592048L;
|
||||
|
||||
public boolean apply(final ObjectName name) {
|
||||
final MBeanServer mbs = QueryEval.getMBeanServer();
|
||||
return isInstanceOf(mbs, name, broadcasterClass);
|
||||
}
|
||||
}
|
||||
private static final QueryExp broadcasterQuery = new BroadcasterQuery();
|
||||
|
||||
private static final NotificationFilter creationFilter;
|
||||
static {
|
||||
NotificationFilterSupport nfs = new NotificationFilterSupport();
|
||||
nfs.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
|
||||
creationFilter = nfs;
|
||||
}
|
||||
|
||||
private final NotificationListener creationListener =
|
||||
new NotificationListener() {
|
||||
public void handleNotification(Notification notif,
|
||||
Object handback) {
|
||||
logger.debug("creationListener", "handleNotification called");
|
||||
createdNotification((MBeanServerNotification) notif);
|
||||
}
|
||||
};
|
||||
|
||||
private void destroyListeners() {
|
||||
checkNoLocks();
|
||||
logger.debug("destroyListeners", "starts");
|
||||
try {
|
||||
removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME,
|
||||
creationListener);
|
||||
} catch (Exception e) {
|
||||
logger.warning("remove listener from MBeanServer delegate", e);
|
||||
}
|
||||
Set<ObjectName> names = queryNames(null, broadcasterQuery);
|
||||
for (final ObjectName name : names) {
|
||||
if (logger.debugOn())
|
||||
logger.debug("destroyListeners",
|
||||
"remove listener from " + name);
|
||||
removeBufferListener(name);
|
||||
}
|
||||
logger.debug("destroyListeners", "ends");
|
||||
}
|
||||
|
||||
private void checkNoLocks() {
|
||||
if (Thread.holdsLock(this) || Thread.holdsLock(globalLock))
|
||||
logger.warning("checkNoLocks", "lock protocol violation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate until we extract the real exception
|
||||
* from a stack of PrivilegedActionExceptions.
|
||||
*/
|
||||
private static Exception extractException(Exception e) {
|
||||
while (e instanceof PrivilegedActionException) {
|
||||
e = ((PrivilegedActionException)e).getException();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc",
|
||||
"ArrayNotificationBuffer");
|
||||
|
||||
private final MBeanServer mBeanServer;
|
||||
private final ArrayQueue<NamedNotification> queue;
|
||||
private int queueSize;
|
||||
private long earliestSequenceNumber;
|
||||
private long nextSequenceNumber;
|
||||
private Set<ObjectName> createdDuringQuery;
|
||||
|
||||
static final String broadcasterClass =
|
||||
NotificationBroadcaster.class.getName();
|
||||
}
|
||||
102
jdkSrc/jdk8/com/sun/jmx/remote/internal/ArrayQueue.java
Normal file
102
jdkSrc/jdk8/com/sun/jmx/remote/internal/ArrayQueue.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class ArrayQueue<T> extends AbstractList<T> {
|
||||
public ArrayQueue(int capacity) {
|
||||
this.capacity = capacity + 1;
|
||||
this.queue = newArray(capacity + 1);
|
||||
this.head = 0;
|
||||
this.tail = 0;
|
||||
}
|
||||
|
||||
public void resize(int newcapacity) {
|
||||
int size = size();
|
||||
if (newcapacity < size)
|
||||
throw new IndexOutOfBoundsException("Resizing would lose data");
|
||||
newcapacity++;
|
||||
if (newcapacity == this.capacity)
|
||||
return;
|
||||
T[] newqueue = newArray(newcapacity);
|
||||
for (int i = 0; i < size; i++)
|
||||
newqueue[i] = get(i);
|
||||
this.capacity = newcapacity;
|
||||
this.queue = newqueue;
|
||||
this.head = 0;
|
||||
this.tail = size;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T[] newArray(int size) {
|
||||
return (T[]) new Object[size];
|
||||
}
|
||||
|
||||
public boolean add(T o) {
|
||||
queue[tail] = o;
|
||||
int newtail = (tail + 1) % capacity;
|
||||
if (newtail == head)
|
||||
throw new IndexOutOfBoundsException("Queue full");
|
||||
tail = newtail;
|
||||
return true; // we did add something
|
||||
}
|
||||
|
||||
public T remove(int i) {
|
||||
if (i != 0)
|
||||
throw new IllegalArgumentException("Can only remove head of queue");
|
||||
if (head == tail)
|
||||
throw new IndexOutOfBoundsException("Queue empty");
|
||||
T removed = queue[head];
|
||||
queue[head] = null;
|
||||
head = (head + 1) % capacity;
|
||||
return removed;
|
||||
}
|
||||
|
||||
public T get(int i) {
|
||||
int size = size();
|
||||
if (i < 0 || i >= size) {
|
||||
final String msg = "Index " + i + ", queue size " + size;
|
||||
throw new IndexOutOfBoundsException(msg);
|
||||
}
|
||||
int index = (head + i) % capacity;
|
||||
return queue[index];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
// Can't use % here because it's not mod: -3 % 2 is -1, not +1.
|
||||
int diff = tail - head;
|
||||
if (diff < 0)
|
||||
diff += capacity;
|
||||
return diff;
|
||||
}
|
||||
|
||||
private int capacity;
|
||||
private T[] queue;
|
||||
private int head;
|
||||
private int tail;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
|
||||
public abstract class ClientCommunicatorAdmin {
|
||||
private static volatile long threadNo = 1;
|
||||
|
||||
public ClientCommunicatorAdmin(long period) {
|
||||
this.period = period;
|
||||
|
||||
if (period > 0) {
|
||||
checker = new Checker();
|
||||
|
||||
Thread t = new Thread(checker, "JMX client heartbeat " + ++threadNo);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
} else
|
||||
checker = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a client to inform of getting an IOException.
|
||||
*/
|
||||
public void gotIOException (IOException ioe) throws IOException {
|
||||
restart(ioe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by this class to check a client connection.
|
||||
*/
|
||||
protected abstract void checkConnection() throws IOException;
|
||||
|
||||
/**
|
||||
* Tells a client to re-start again.
|
||||
*/
|
||||
protected abstract void doStart() throws IOException;
|
||||
|
||||
/**
|
||||
* Tells a client to stop because failing to call checkConnection.
|
||||
*/
|
||||
protected abstract void doStop();
|
||||
|
||||
/**
|
||||
* Terminates this object.
|
||||
*/
|
||||
public void terminate() {
|
||||
synchronized(lock) {
|
||||
if (state == TERMINATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = TERMINATED;
|
||||
|
||||
lock.notifyAll();
|
||||
|
||||
if (checker != null)
|
||||
checker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void restart(IOException ioe) throws IOException {
|
||||
// check state
|
||||
synchronized(lock) {
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("The client has been closed.");
|
||||
} else if (state == FAILED) { // already failed to re-start by another thread
|
||||
throw ioe;
|
||||
} else if (state == RE_CONNECTING) {
|
||||
// restart process has been called by another thread
|
||||
// we need to wait
|
||||
while(state == RE_CONNECTING) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException ire) {
|
||||
// be asked to give up
|
||||
InterruptedIOException iioe = new InterruptedIOException(ire.toString());
|
||||
EnvHelp.initCause(iioe, ire);
|
||||
|
||||
throw iioe;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("The client has been closed.");
|
||||
} else if (state != CONNECTED) {
|
||||
// restarted is failed by another thread
|
||||
throw ioe;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
state = RE_CONNECTING;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
// re-starting
|
||||
try {
|
||||
doStart();
|
||||
synchronized(lock) {
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("The client has been closed.");
|
||||
}
|
||||
|
||||
state = CONNECTED;
|
||||
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
logger.warning("restart", "Failed to restart: " + e);
|
||||
logger.debug("restart",e);
|
||||
|
||||
synchronized(lock) {
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("The client has been closed.");
|
||||
}
|
||||
|
||||
state = FAILED;
|
||||
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
try {
|
||||
doStop();
|
||||
} catch (Exception eee) {
|
||||
// OK.
|
||||
// We know there is a problem.
|
||||
}
|
||||
|
||||
terminate();
|
||||
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// private varaibles
|
||||
// --------------------------------------------------------------
|
||||
private class Checker implements Runnable {
|
||||
public void run() {
|
||||
myThread = Thread.currentThread();
|
||||
|
||||
while (state != TERMINATED && !myThread.isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(period);
|
||||
} catch (InterruptedException ire) {
|
||||
// OK.
|
||||
// We will check the state at the following steps
|
||||
}
|
||||
|
||||
if (state == TERMINATED || myThread.isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
checkConnection();
|
||||
} catch (Exception e) {
|
||||
synchronized(lock) {
|
||||
if (state == TERMINATED || myThread.isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
e = (Exception)EnvHelp.getCause(e);
|
||||
|
||||
if (e instanceof IOException &&
|
||||
!(e instanceof InterruptedIOException)) {
|
||||
try {
|
||||
gotIOException((IOException)e);
|
||||
} catch (Exception ee) {
|
||||
logger.warning("Checker-run",
|
||||
"Failed to check connection: "+ e);
|
||||
logger.warning("Checker-run", "stopping");
|
||||
logger.debug("Checker-run",e);
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.warning("Checker-run",
|
||||
"Failed to check the connection: " + e);
|
||||
logger.debug("Checker-run",e);
|
||||
|
||||
// XXX stop checking?
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Checker-run", "Finished.");
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (myThread != null && myThread != Thread.currentThread()) {
|
||||
myThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private Thread myThread;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// private variables
|
||||
// --------------------------------------------------------------
|
||||
private final Checker checker;
|
||||
private long period;
|
||||
|
||||
// state
|
||||
private final static int CONNECTED = 0;
|
||||
private final static int RE_CONNECTING = 1;
|
||||
private final static int FAILED = 2;
|
||||
private final static int TERMINATED = 3;
|
||||
|
||||
private int state = CONNECTED;
|
||||
|
||||
private final int[] lock = new int[0];
|
||||
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc",
|
||||
"ClientCommunicatorAdmin");
|
||||
}
|
||||
106
jdkSrc/jdk8/com/sun/jmx/remote/internal/ClientListenerInfo.java
Normal file
106
jdkSrc/jdk8/com/sun/jmx/remote/internal/ClientListenerInfo.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import javax.management.NotificationFilter;
|
||||
import javax.management.NotificationListener;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
|
||||
/**
|
||||
* <p>An identified listener. A listener has an Integer id that is
|
||||
* unique per connector server. It selects notifications based on the
|
||||
* ObjectName of the originator and an optional
|
||||
* NotificationFilter.</p>
|
||||
*/
|
||||
public class ClientListenerInfo {
|
||||
public ClientListenerInfo(Integer listenerID,
|
||||
ObjectName name,
|
||||
NotificationListener listener,
|
||||
NotificationFilter filter,
|
||||
Object handback,
|
||||
Subject delegationSubject) {
|
||||
this.listenerID = listenerID;
|
||||
this.name = name;
|
||||
this.listener = listener;
|
||||
this.filter = filter;
|
||||
this.handback = handback;
|
||||
this.delegationSubject = delegationSubject;
|
||||
}
|
||||
|
||||
public ObjectName getObjectName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getListenerID() {
|
||||
return listenerID;
|
||||
}
|
||||
|
||||
public NotificationFilter getNotificationFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public NotificationListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public Object getHandback() {
|
||||
return handback;
|
||||
}
|
||||
|
||||
public Subject getDelegationSubject() {
|
||||
return delegationSubject;
|
||||
}
|
||||
|
||||
|
||||
public boolean sameAs(ObjectName name) {
|
||||
return (getObjectName().equals(name));
|
||||
}
|
||||
|
||||
|
||||
public boolean sameAs(ObjectName name, NotificationListener listener) {
|
||||
return ( getObjectName().equals(name) &&
|
||||
getListener() == listener);
|
||||
}
|
||||
|
||||
|
||||
public boolean sameAs(ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) {
|
||||
return ( getObjectName().equals(name) &&
|
||||
getListener() == listener &&
|
||||
getNotificationFilter() == filter &&
|
||||
getHandback() == handback);
|
||||
}
|
||||
|
||||
private final ObjectName name;
|
||||
private final Integer listenerID;
|
||||
private final NotificationFilter filter;
|
||||
|
||||
private final NotificationListener listener;
|
||||
private final Object handback;
|
||||
private final Subject delegationSubject;
|
||||
}
|
||||
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationListener;
|
||||
import javax.management.NotificationFilter;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.MBeanServerNotification;
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.ListenerNotFoundException;
|
||||
|
||||
import javax.management.remote.NotificationResult;
|
||||
import javax.management.remote.TargetedNotification;
|
||||
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
import java.rmi.UnmarshalException;
|
||||
|
||||
|
||||
public abstract class ClientNotifForwarder {
|
||||
|
||||
private final AccessControlContext acc;
|
||||
|
||||
public ClientNotifForwarder(Map env) {
|
||||
this(null, env);
|
||||
}
|
||||
|
||||
private static int threadId;
|
||||
|
||||
/* An Executor that allows at most one executing and one pending
|
||||
Runnable. It uses at most one thread -- as soon as there is
|
||||
no pending Runnable the thread can exit. Another thread is
|
||||
created as soon as there is a new pending Runnable. This
|
||||
Executor is adapted for use in a situation where each Runnable
|
||||
usually schedules up another Runnable. On return from the
|
||||
first one, the second one is immediately executed. So this
|
||||
just becomes a complicated way to write a while loop, but with
|
||||
the advantage that you can replace it with another Executor,
|
||||
for instance one that you are using to execute a bunch of other
|
||||
unrelated work.
|
||||
|
||||
You might expect that a java.util.concurrent.ThreadPoolExecutor
|
||||
with corePoolSize=0 and maximumPoolSize=1 would have the same
|
||||
behavior, but it does not. A ThreadPoolExecutor only creates
|
||||
a new thread when a new task is submitted and the number of
|
||||
existing threads is < corePoolSize. This can never happen when
|
||||
corePoolSize=0, so new threads are never created. Surprising,
|
||||
but there you are.
|
||||
*/
|
||||
private static class LinearExecutor implements Executor {
|
||||
public synchronized void execute(Runnable command) {
|
||||
if (this.command != null)
|
||||
throw new IllegalArgumentException("More than one command");
|
||||
this.command = command;
|
||||
if (thread == null) {
|
||||
thread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
Runnable r;
|
||||
synchronized (LinearExecutor.this) {
|
||||
if (LinearExecutor.this.command == null) {
|
||||
thread = null;
|
||||
return;
|
||||
} else {
|
||||
r = LinearExecutor.this.command;
|
||||
LinearExecutor.this.command = null;
|
||||
}
|
||||
}
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
};
|
||||
thread.setDaemon(true);
|
||||
thread.setName("ClientNotifForwarder-" + ++threadId);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable command;
|
||||
private Thread thread;
|
||||
}
|
||||
|
||||
public ClientNotifForwarder(ClassLoader defaultClassLoader, Map<String, ?> env) {
|
||||
maxNotifications = EnvHelp.getMaxFetchNotifNumber(env);
|
||||
timeout = EnvHelp.getFetchTimeout(env);
|
||||
|
||||
/* You can supply an Executor in which the remote call to
|
||||
fetchNotifications will be made. The Executor's execute
|
||||
method reschedules another task, so you must not use
|
||||
an Executor that executes tasks in the caller's thread. */
|
||||
Executor ex = (Executor)
|
||||
env.get("jmx.remote.x.fetch.notifications.executor");
|
||||
if (ex == null)
|
||||
ex = new LinearExecutor();
|
||||
else if (logger.traceOn())
|
||||
logger.trace("ClientNotifForwarder", "executor is " + ex);
|
||||
|
||||
this.defaultClassLoader = defaultClassLoader;
|
||||
this.executor = ex;
|
||||
this.acc = AccessController.getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to to fetch notifications from a server.
|
||||
*/
|
||||
abstract protected NotificationResult fetchNotifs(long clientSequenceNumber,
|
||||
int maxNotifications,
|
||||
long timeout)
|
||||
throws IOException, ClassNotFoundException;
|
||||
|
||||
abstract protected Integer addListenerForMBeanRemovedNotif()
|
||||
throws IOException, InstanceNotFoundException;
|
||||
|
||||
abstract protected void removeListenerForMBeanRemovedNotif(Integer id)
|
||||
throws IOException, InstanceNotFoundException,
|
||||
ListenerNotFoundException;
|
||||
|
||||
/**
|
||||
* Used to send out a notification about lost notifs
|
||||
*/
|
||||
abstract protected void lostNotifs(String message, long number);
|
||||
|
||||
|
||||
public synchronized void addNotificationListener(Integer listenerID,
|
||||
ObjectName name,
|
||||
NotificationListener listener,
|
||||
NotificationFilter filter,
|
||||
Object handback,
|
||||
Subject delegationSubject)
|
||||
throws IOException, InstanceNotFoundException {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("addNotificationListener",
|
||||
"Add the listener "+listener+" at "+name);
|
||||
}
|
||||
|
||||
infoList.put(listenerID,
|
||||
new ClientListenerInfo(listenerID,
|
||||
name,
|
||||
listener,
|
||||
filter,
|
||||
handback,
|
||||
delegationSubject));
|
||||
|
||||
|
||||
init(false);
|
||||
}
|
||||
|
||||
public synchronized Integer[]
|
||||
removeNotificationListener(ObjectName name,
|
||||
NotificationListener listener)
|
||||
throws ListenerNotFoundException, IOException {
|
||||
|
||||
beforeRemove();
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("removeNotificationListener",
|
||||
"Remove the listener "+listener+" from "+name);
|
||||
}
|
||||
|
||||
List<Integer> ids = new ArrayList<Integer>();
|
||||
List<ClientListenerInfo> values =
|
||||
new ArrayList<ClientListenerInfo>(infoList.values());
|
||||
for (int i=values.size()-1; i>=0; i--) {
|
||||
ClientListenerInfo li = values.get(i);
|
||||
|
||||
if (li.sameAs(name, listener)) {
|
||||
ids.add(li.getListenerID());
|
||||
|
||||
infoList.remove(li.getListenerID());
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.isEmpty())
|
||||
throw new ListenerNotFoundException("Listener not found");
|
||||
|
||||
return ids.toArray(new Integer[0]);
|
||||
}
|
||||
|
||||
public synchronized Integer
|
||||
removeNotificationListener(ObjectName name,
|
||||
NotificationListener listener,
|
||||
NotificationFilter filter,
|
||||
Object handback)
|
||||
throws ListenerNotFoundException, IOException {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("removeNotificationListener",
|
||||
"Remove the listener "+listener+" from "+name);
|
||||
}
|
||||
|
||||
beforeRemove();
|
||||
|
||||
Integer id = null;
|
||||
|
||||
List<ClientListenerInfo> values =
|
||||
new ArrayList<ClientListenerInfo>(infoList.values());
|
||||
for (int i=values.size()-1; i>=0; i--) {
|
||||
ClientListenerInfo li = values.get(i);
|
||||
if (li.sameAs(name, listener, filter, handback)) {
|
||||
id=li.getListenerID();
|
||||
|
||||
infoList.remove(id);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
throw new ListenerNotFoundException("Listener not found");
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized Integer[] removeNotificationListener(ObjectName name) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("removeNotificationListener",
|
||||
"Remove all listeners registered at "+name);
|
||||
}
|
||||
|
||||
List<Integer> ids = new ArrayList<Integer>();
|
||||
|
||||
List<ClientListenerInfo> values =
|
||||
new ArrayList<ClientListenerInfo>(infoList.values());
|
||||
for (int i=values.size()-1; i>=0; i--) {
|
||||
ClientListenerInfo li = values.get(i);
|
||||
if (li.sameAs(name)) {
|
||||
ids.add(li.getListenerID());
|
||||
|
||||
infoList.remove(li.getListenerID());
|
||||
}
|
||||
}
|
||||
|
||||
return ids.toArray(new Integer[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a connector is doing reconnection. Like <code>postReconnection</code>,
|
||||
* this method is intended to be called only by a client connector:
|
||||
* <code>RMIConnector</code> and <code>ClientIntermediary</code>.
|
||||
* Call this method will set the flag beingReconnection to <code>true</code>,
|
||||
* and the thread used to fetch notifis will be stopped, a new thread can be
|
||||
* created only after the method <code>postReconnection</code> is called.
|
||||
*
|
||||
* It is caller's responsiblity to not re-call this method before calling
|
||||
* <code>postReconnection</code>.
|
||||
*/
|
||||
public synchronized ClientListenerInfo[] preReconnection() throws IOException {
|
||||
if (state == TERMINATED || beingReconnected) { // should never
|
||||
throw new IOException("Illegal state.");
|
||||
}
|
||||
|
||||
final ClientListenerInfo[] tmp =
|
||||
infoList.values().toArray(new ClientListenerInfo[0]);
|
||||
|
||||
|
||||
beingReconnected = true;
|
||||
|
||||
infoList.clear();
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after reconnection is finished.
|
||||
* This method is intended to be called only by a client connector:
|
||||
* <code>RMIConnector</code> and <code>ClientIntermediary</code>.
|
||||
*/
|
||||
public synchronized void postReconnection(ClientListenerInfo[] listenerInfos)
|
||||
throws IOException {
|
||||
|
||||
if (state == TERMINATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (state == STOPPING) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ire) {
|
||||
IOException ioe = new IOException(ire.toString());
|
||||
EnvHelp.initCause(ioe, ire);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean trace = logger.traceOn();
|
||||
final int len = listenerInfos.length;
|
||||
|
||||
for (int i=0; i<len; i++) {
|
||||
if (trace) {
|
||||
logger.trace("addNotificationListeners",
|
||||
"Add a listener at "+
|
||||
listenerInfos[i].getListenerID());
|
||||
}
|
||||
|
||||
infoList.put(listenerInfos[i].getListenerID(), listenerInfos[i]);
|
||||
}
|
||||
|
||||
beingReconnected = false;
|
||||
notifyAll();
|
||||
|
||||
if (currentFetchThread == Thread.currentThread() ||
|
||||
state == STARTING || state == STARTED) { // doing or waiting reconnection
|
||||
// only update mbeanRemovedNotifID
|
||||
try {
|
||||
mbeanRemovedNotifID = addListenerForMBeanRemovedNotif();
|
||||
} catch (Exception e) {
|
||||
final String msg =
|
||||
"Failed to register a listener to the mbean " +
|
||||
"server: the client will not do clean when an MBean " +
|
||||
"is unregistered";
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("init", msg, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (state == STOPPING) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ire) {
|
||||
IOException ioe = new IOException(ire.toString());
|
||||
EnvHelp.initCause(ioe, ire);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
if (listenerInfos.length > 0) { // old listeners are re-added
|
||||
init(true); // not update clientSequenceNumber
|
||||
} else if (infoList.size() > 0) { // only new listeners added during reconnection
|
||||
init(false); // need update clientSequenceNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void terminate() {
|
||||
if (state == TERMINATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("terminate", "Terminating...");
|
||||
}
|
||||
|
||||
if (state == STARTED) {
|
||||
infoList.clear();
|
||||
}
|
||||
|
||||
setState(TERMINATED);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// private classes
|
||||
// -------------------------------------------------
|
||||
//
|
||||
|
||||
private class NotifFetcher implements Runnable {
|
||||
|
||||
private volatile boolean alreadyLogged = false;
|
||||
|
||||
private void logOnce(String msg, SecurityException x) {
|
||||
if (alreadyLogged) return;
|
||||
// Log only once.
|
||||
logger.config("setContextClassLoader",msg);
|
||||
if (x != null) logger.fine("setContextClassLoader", x);
|
||||
alreadyLogged = true;
|
||||
}
|
||||
|
||||
// Set new context class loader, returns previous one.
|
||||
private final ClassLoader setContextClassLoader(final ClassLoader loader) {
|
||||
final AccessControlContext ctxt = ClientNotifForwarder.this.acc;
|
||||
// if ctxt is null, log a config message and throw a
|
||||
// SecurityException.
|
||||
if (ctxt == null) {
|
||||
logOnce("AccessControlContext must not be null.",null);
|
||||
throw new SecurityException("AccessControlContext must not be null");
|
||||
}
|
||||
return AccessController.doPrivileged(
|
||||
new PrivilegedAction<ClassLoader>() {
|
||||
public ClassLoader run() {
|
||||
try {
|
||||
// get context class loader - may throw
|
||||
// SecurityException - though unlikely.
|
||||
final ClassLoader previous =
|
||||
Thread.currentThread().getContextClassLoader();
|
||||
|
||||
// if nothing needs to be done, break here...
|
||||
if (loader == previous) return previous;
|
||||
|
||||
// reset context class loader - may throw
|
||||
// SecurityException
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
return previous;
|
||||
} catch (SecurityException x) {
|
||||
logOnce("Permission to set ContextClassLoader missing. " +
|
||||
"Notifications will not be dispatched. " +
|
||||
"Please check your Java policy configuration: " +
|
||||
x, x);
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
}, ctxt);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
final ClassLoader previous;
|
||||
if (defaultClassLoader != null) {
|
||||
previous = setContextClassLoader(defaultClassLoader);
|
||||
} else {
|
||||
previous = null;
|
||||
}
|
||||
try {
|
||||
doRun();
|
||||
} finally {
|
||||
if (defaultClassLoader != null) {
|
||||
setContextClassLoader(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doRun() {
|
||||
synchronized (ClientNotifForwarder.this) {
|
||||
currentFetchThread = Thread.currentThread();
|
||||
|
||||
if (state == STARTING) {
|
||||
setState(STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NotificationResult nr = null;
|
||||
if (!shouldStop() && (nr = fetchNotifs()) != null) {
|
||||
// nr == null means got exception
|
||||
|
||||
final TargetedNotification[] notifs =
|
||||
nr.getTargetedNotifications();
|
||||
final int len = notifs.length;
|
||||
final Map<Integer, ClientListenerInfo> listeners;
|
||||
final Integer myListenerID;
|
||||
|
||||
long missed = 0;
|
||||
|
||||
synchronized(ClientNotifForwarder.this) {
|
||||
// check sequence number.
|
||||
//
|
||||
if (clientSequenceNumber >= 0) {
|
||||
missed = nr.getEarliestSequenceNumber() -
|
||||
clientSequenceNumber;
|
||||
}
|
||||
|
||||
clientSequenceNumber = nr.getNextSequenceNumber();
|
||||
|
||||
listeners = new HashMap<Integer, ClientListenerInfo>();
|
||||
|
||||
for (int i = 0 ; i < len ; i++) {
|
||||
final TargetedNotification tn = notifs[i];
|
||||
final Integer listenerID = tn.getListenerID();
|
||||
|
||||
// check if an mbean unregistration notif
|
||||
if (!listenerID.equals(mbeanRemovedNotifID)) {
|
||||
final ClientListenerInfo li = infoList.get(listenerID);
|
||||
if (li != null) {
|
||||
listeners.put(listenerID, li);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final Notification notif = tn.getNotification();
|
||||
final String unreg =
|
||||
MBeanServerNotification.UNREGISTRATION_NOTIFICATION;
|
||||
if (notif instanceof MBeanServerNotification &&
|
||||
notif.getType().equals(unreg)) {
|
||||
|
||||
MBeanServerNotification mbsn =
|
||||
(MBeanServerNotification) notif;
|
||||
ObjectName name = mbsn.getMBeanName();
|
||||
|
||||
removeNotificationListener(name);
|
||||
}
|
||||
}
|
||||
myListenerID = mbeanRemovedNotifID;
|
||||
}
|
||||
|
||||
if (missed > 0) {
|
||||
final String msg =
|
||||
"May have lost up to " + missed +
|
||||
" notification" + (missed == 1 ? "" : "s");
|
||||
lostNotifs(msg, missed);
|
||||
logger.trace("NotifFetcher.run", msg);
|
||||
}
|
||||
|
||||
// forward
|
||||
for (int i = 0 ; i < len ; i++) {
|
||||
final TargetedNotification tn = notifs[i];
|
||||
dispatchNotification(tn,myListenerID,listeners);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (ClientNotifForwarder.this) {
|
||||
currentFetchThread = null;
|
||||
}
|
||||
|
||||
if (nr == null) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("NotifFetcher-run",
|
||||
"Recieved null object as notifs, stops fetching because the "
|
||||
+ "notification server is terminated.");
|
||||
}
|
||||
}
|
||||
if (nr == null || shouldStop()) {
|
||||
// tell that the thread is REALLY stopped
|
||||
setState(STOPPED);
|
||||
|
||||
try {
|
||||
removeListenerForMBeanRemovedNotif(mbeanRemovedNotifID);
|
||||
} catch (Exception e) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("NotifFetcher-run",
|
||||
"removeListenerForMBeanRemovedNotif", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
executor.execute(this);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchNotification(TargetedNotification tn,
|
||||
Integer myListenerID,
|
||||
Map<Integer, ClientListenerInfo> listeners) {
|
||||
final Notification notif = tn.getNotification();
|
||||
final Integer listenerID = tn.getListenerID();
|
||||
|
||||
if (listenerID.equals(myListenerID)) return;
|
||||
final ClientListenerInfo li = listeners.get(listenerID);
|
||||
|
||||
if (li == null) {
|
||||
logger.trace("NotifFetcher.dispatch",
|
||||
"Listener ID not in map");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationListener l = li.getListener();
|
||||
Object h = li.getHandback();
|
||||
try {
|
||||
l.handleNotification(notif, h);
|
||||
} catch (RuntimeException e) {
|
||||
final String msg =
|
||||
"Failed to forward a notification " +
|
||||
"to a listener";
|
||||
logger.trace("NotifFetcher-run", msg, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private NotificationResult fetchNotifs() {
|
||||
try {
|
||||
NotificationResult nr = ClientNotifForwarder.this.
|
||||
fetchNotifs(clientSequenceNumber,maxNotifications,
|
||||
timeout);
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("NotifFetcher-run",
|
||||
"Got notifications from the server: "+nr);
|
||||
}
|
||||
|
||||
return nr;
|
||||
} catch (ClassNotFoundException | NotSerializableException | UnmarshalException e) {
|
||||
logger.trace("NotifFetcher.fetchNotifs", e);
|
||||
return fetchOneNotif();
|
||||
} catch (IOException ioe) {
|
||||
if (!shouldStop()) {
|
||||
logger.error("NotifFetcher-run",
|
||||
"Failed to fetch notification, " +
|
||||
"stopping thread. Error is: " + ioe, ioe);
|
||||
logger.debug("NotifFetcher-run",ioe);
|
||||
}
|
||||
|
||||
// no more fetching
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fetch one notification when we suspect that it might be a
|
||||
notification that we can't deserialize (because of a
|
||||
missing class). First we ask for 0 notifications with 0
|
||||
timeout. This allows us to skip sequence numbers for
|
||||
notifications that don't match our filters. Then we ask
|
||||
for one notification. If that produces a
|
||||
ClassNotFoundException, NotSerializableException or
|
||||
UnmarshalException, we increase our sequence number and ask again.
|
||||
Eventually we will either get a successful notification, or a
|
||||
return with 0 notifications. In either case we can return a
|
||||
NotificationResult. This algorithm works (albeit less
|
||||
well) even if the server implementation doesn't optimize a
|
||||
request for 0 notifications to skip sequence numbers for
|
||||
notifications that don't match our filters.
|
||||
|
||||
If we had at least one
|
||||
ClassNotFoundException/NotSerializableException/UnmarshalException,
|
||||
then we must emit a JMXConnectionNotification.LOST_NOTIFS.
|
||||
*/
|
||||
private NotificationResult fetchOneNotif() {
|
||||
ClientNotifForwarder cnf = ClientNotifForwarder.this;
|
||||
|
||||
long startSequenceNumber = clientSequenceNumber;
|
||||
|
||||
int notFoundCount = 0;
|
||||
|
||||
NotificationResult result = null;
|
||||
long firstEarliest = -1;
|
||||
|
||||
while (result == null && !shouldStop()) {
|
||||
NotificationResult nr;
|
||||
|
||||
try {
|
||||
// 0 notifs to update startSequenceNumber
|
||||
nr = cnf.fetchNotifs(startSequenceNumber, 0, 0L);
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.warning("NotifFetcher.fetchOneNotif",
|
||||
"Impossible exception: " + e);
|
||||
logger.debug("NotifFetcher.fetchOneNotif",e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (!shouldStop())
|
||||
logger.trace("NotifFetcher.fetchOneNotif", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shouldStop() || nr == null)
|
||||
return null;
|
||||
|
||||
startSequenceNumber = nr.getNextSequenceNumber();
|
||||
if (firstEarliest < 0)
|
||||
firstEarliest = nr.getEarliestSequenceNumber();
|
||||
|
||||
try {
|
||||
// 1 notif to skip possible missing class
|
||||
result = cnf.fetchNotifs(startSequenceNumber, 1, 0L);
|
||||
} catch (ClassNotFoundException | NotSerializableException | UnmarshalException e) {
|
||||
logger.warning("NotifFetcher.fetchOneNotif",
|
||||
"Failed to deserialize a notification: "+e.toString());
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("NotifFetcher.fetchOneNotif",
|
||||
"Failed to deserialize a notification.", e);
|
||||
}
|
||||
|
||||
notFoundCount++;
|
||||
startSequenceNumber++;
|
||||
} catch (Exception e) {
|
||||
if (!shouldStop())
|
||||
logger.trace("NotifFetcher.fetchOneNotif", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (notFoundCount > 0) {
|
||||
final String msg =
|
||||
"Dropped " + notFoundCount + " notification" +
|
||||
(notFoundCount == 1 ? "" : "s") +
|
||||
" because classes were missing locally or incompatible";
|
||||
lostNotifs(msg, notFoundCount);
|
||||
// Even if result.getEarliestSequenceNumber() is now greater than
|
||||
// it was initially, meaning some notifs have been dropped
|
||||
// from the buffer, we don't want the caller to see that
|
||||
// because it is then likely to renotify about the lost notifs.
|
||||
// So we put back the first value of earliestSequenceNumber
|
||||
// that we saw.
|
||||
if (result != null) {
|
||||
result = new NotificationResult(
|
||||
firstEarliest, result.getNextSequenceNumber(),
|
||||
result.getTargetedNotifications());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean shouldStop() {
|
||||
synchronized (ClientNotifForwarder.this) {
|
||||
if (state != STARTED) {
|
||||
return true;
|
||||
} else if (infoList.size() == 0) {
|
||||
// no more listener, stop fetching
|
||||
setState(STOPPING);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// private methods
|
||||
// -------------------------------------------------
|
||||
private synchronized void setState(int newState) {
|
||||
if (state == TERMINATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = newState;
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* Called to decide whether need to start a thread for fetching notifs.
|
||||
* <P>The parameter reconnected will decide whether to initilize the clientSequenceNumber,
|
||||
* initilaizing the clientSequenceNumber means to ignore all notifications arrived before.
|
||||
* If it is reconnected, we will not initialize in order to get all notifications arrived
|
||||
* during the reconnection. It may cause the newly registered listeners to receive some
|
||||
* notifications arrived before its registray.
|
||||
*/
|
||||
private synchronized void init(boolean reconnected) throws IOException {
|
||||
switch (state) {
|
||||
case STARTED:
|
||||
return;
|
||||
case STARTING:
|
||||
return;
|
||||
case TERMINATED:
|
||||
throw new IOException("The ClientNotifForwarder has been terminated.");
|
||||
case STOPPING:
|
||||
if (beingReconnected == true) {
|
||||
// wait for another thread to do, which is doing reconnection
|
||||
return;
|
||||
}
|
||||
|
||||
while (state == STOPPING) { // make sure only one fetching thread.
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ire) {
|
||||
IOException ioe = new IOException(ire.toString());
|
||||
EnvHelp.initCause(ioe, ire);
|
||||
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
// re-call this method to check the state again,
|
||||
// the state can be other value like TERMINATED.
|
||||
init(reconnected);
|
||||
|
||||
return;
|
||||
case STOPPED:
|
||||
if (beingReconnected == true) {
|
||||
// wait for another thread to do, which is doing reconnection
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("init", "Initializing...");
|
||||
}
|
||||
|
||||
// init the clientSequenceNumber if not reconnected.
|
||||
if (!reconnected) {
|
||||
try {
|
||||
NotificationResult nr = fetchNotifs(-1, 0, 0);
|
||||
|
||||
if (state != STOPPED) { // JDK-8038940
|
||||
// reconnection must happen during
|
||||
// fetchNotifs(-1, 0, 0), and a new
|
||||
// thread takes over the fetching job
|
||||
return;
|
||||
}
|
||||
|
||||
clientSequenceNumber = nr.getNextSequenceNumber();
|
||||
} catch (ClassNotFoundException e) {
|
||||
// can't happen
|
||||
logger.warning("init", "Impossible exception: "+ e);
|
||||
logger.debug("init",e);
|
||||
}
|
||||
}
|
||||
|
||||
// for cleaning
|
||||
try {
|
||||
mbeanRemovedNotifID = addListenerForMBeanRemovedNotif();
|
||||
} catch (Exception e) {
|
||||
final String msg =
|
||||
"Failed to register a listener to the mbean " +
|
||||
"server: the client will not do clean when an MBean " +
|
||||
"is unregistered";
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("init", msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
setState(STARTING);
|
||||
|
||||
// start fetching
|
||||
executor.execute(new NotifFetcher());
|
||||
|
||||
return;
|
||||
default:
|
||||
// should not
|
||||
throw new IOException("Unknown state.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import: should not remove a listener during reconnection, the reconnection
|
||||
* needs to change the listener list and that will possibly make removal fail.
|
||||
*/
|
||||
private synchronized void beforeRemove() throws IOException {
|
||||
while (beingReconnected) {
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("Terminated.");
|
||||
}
|
||||
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ire) {
|
||||
IOException ioe = new IOException(ire.toString());
|
||||
EnvHelp.initCause(ioe, ire);
|
||||
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == TERMINATED) {
|
||||
throw new IOException("Terminated.");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// private variables
|
||||
// -------------------------------------------------
|
||||
|
||||
private final ClassLoader defaultClassLoader;
|
||||
private final Executor executor;
|
||||
|
||||
private final Map<Integer, ClientListenerInfo> infoList =
|
||||
new HashMap<Integer, ClientListenerInfo>();
|
||||
|
||||
// notif stuff
|
||||
private long clientSequenceNumber = -1;
|
||||
private final int maxNotifications;
|
||||
private final long timeout;
|
||||
private Integer mbeanRemovedNotifID = null;
|
||||
private Thread currentFetchThread;
|
||||
|
||||
// state
|
||||
/**
|
||||
* This state means that a thread is being created for fetching and forwarding notifications.
|
||||
*/
|
||||
private static final int STARTING = 0;
|
||||
|
||||
/**
|
||||
* This state tells that a thread has been started for fetching and forwarding notifications.
|
||||
*/
|
||||
private static final int STARTED = 1;
|
||||
|
||||
/**
|
||||
* This state means that the fetching thread is informed to stop.
|
||||
*/
|
||||
private static final int STOPPING = 2;
|
||||
|
||||
/**
|
||||
* This state means that the fetching thread is already stopped.
|
||||
*/
|
||||
private static final int STOPPED = 3;
|
||||
|
||||
/**
|
||||
* This state means that this object is terminated and no more thread will be created
|
||||
* for fetching notifications.
|
||||
*/
|
||||
private static final int TERMINATED = 4;
|
||||
|
||||
private int state = STOPPED;
|
||||
|
||||
/**
|
||||
* This variable is used to tell whether a connector (RMIConnector or ClientIntermediary)
|
||||
* is doing reconnection.
|
||||
* This variable will be set to true by the method <code>preReconnection</code>, and set
|
||||
* to false by <code>postReconnection</code>.
|
||||
* When beingReconnected == true, no thread will be created for fetching notifications.
|
||||
*/
|
||||
private boolean beingReconnected = false;
|
||||
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc",
|
||||
"ClientNotifForwarder");
|
||||
}
|
||||
190
jdkSrc/jdk8/com/sun/jmx/remote/internal/IIOPHelper.java
Normal file
190
jdkSrc/jdk8/com/sun/jmx/remote/internal/IIOPHelper.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.io.IOException;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.NoSuchObjectException;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
/**
|
||||
* A helper class for RMI-IIOP and CORBA APIs.
|
||||
*/
|
||||
|
||||
public final class IIOPHelper {
|
||||
private IIOPHelper() { }
|
||||
|
||||
// loads IIOPProxy implementation class if available
|
||||
private static final String IMPL_CLASS =
|
||||
"com.sun.jmx.remote.protocol.iiop.IIOPProxyImpl";
|
||||
private static final IIOPProxy proxy =
|
||||
AccessController.doPrivileged(new PrivilegedAction<IIOPProxy>() {
|
||||
public IIOPProxy run() {
|
||||
try {
|
||||
Class<?> c = Class.forName(IMPL_CLASS, true,
|
||||
IIOPHelper.class.getClassLoader());
|
||||
return (IIOPProxy)c.newInstance();
|
||||
} catch (ClassNotFoundException cnf) {
|
||||
return null;
|
||||
} catch (InstantiationException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}});
|
||||
|
||||
/**
|
||||
* Returns true if RMI-IIOP and CORBA is available.
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return proxy != null;
|
||||
}
|
||||
|
||||
private static void ensureAvailable() {
|
||||
if (proxy == null)
|
||||
throw new AssertionError("Should not here");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a Stub.
|
||||
*/
|
||||
public static boolean isStub(Object obj) {
|
||||
return (proxy == null) ? false : proxy.isStub(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Delegate to which the given Stub delegates.
|
||||
*/
|
||||
public static Object getDelegate(Object stub) {
|
||||
ensureAvailable();
|
||||
return proxy.getDelegate(stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Delegate for a given Stub.
|
||||
*/
|
||||
public static void setDelegate(Object stub, Object delegate) {
|
||||
ensureAvailable();
|
||||
proxy.setDelegate(stub, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ORB associated with the given stub
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
* if the object does not support the operation that
|
||||
* was invoked
|
||||
*/
|
||||
public static Object getOrb(Object stub) {
|
||||
ensureAvailable();
|
||||
return proxy.getOrb(stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the Stub to the given ORB.
|
||||
*/
|
||||
public static void connect(Object stub, Object orb)
|
||||
throws IOException
|
||||
{
|
||||
if (proxy == null)
|
||||
throw new IOException("Connection to ORB failed, RMI/IIOP not available");
|
||||
proxy.connect(stub, orb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given object is an ORB.
|
||||
*/
|
||||
public static boolean isOrb(Object obj) {
|
||||
return (proxy == null) ? false : proxy.isOrb(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates, and returns, a new ORB instance.
|
||||
*/
|
||||
public static Object createOrb(String[] args, Properties props)
|
||||
throws IOException
|
||||
{
|
||||
if (proxy == null)
|
||||
throw new IOException("ORB initialization failed, RMI/IIOP not available");
|
||||
return proxy.createOrb(args, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string, produced by the object_to_string method, back
|
||||
* to a CORBA object reference.
|
||||
*/
|
||||
public static Object stringToObject(Object orb, String str) {
|
||||
ensureAvailable();
|
||||
return proxy.stringToObject(orb, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given CORBA object reference to a string.
|
||||
*/
|
||||
public static String objectToString(Object orb, Object obj) {
|
||||
ensureAvailable();
|
||||
return proxy.objectToString(orb, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to ensure that an object of a remote or abstract interface
|
||||
* type can be cast to a desired type.
|
||||
*/
|
||||
public static <T> T narrow(Object narrowFrom, Class<T> narrowTo) {
|
||||
ensureAvailable();
|
||||
return proxy.narrow(narrowFrom, narrowTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a server object ready to receive remote calls
|
||||
*/
|
||||
public static void exportObject(Remote obj) throws IOException {
|
||||
if (proxy == null)
|
||||
throw new IOException("RMI object cannot be exported, RMI/IIOP not available");
|
||||
proxy.exportObject(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters a server object from the runtime.
|
||||
*/
|
||||
public static void unexportObject(Remote obj) throws IOException {
|
||||
if (proxy == null)
|
||||
throw new NoSuchObjectException("Object not exported");
|
||||
proxy.unexportObject(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stub for the given server object.
|
||||
*/
|
||||
public static Remote toStub(Remote obj) throws IOException {
|
||||
if (proxy == null)
|
||||
throw new NoSuchObjectException("Object not exported");
|
||||
return proxy.toStub(obj);
|
||||
}
|
||||
}
|
||||
110
jdkSrc/jdk8/com/sun/jmx/remote/internal/IIOPProxy.java
Normal file
110
jdkSrc/jdk8/com/sun/jmx/remote/internal/IIOPProxy.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.NoSuchObjectException;
|
||||
|
||||
/**
|
||||
* An interface to a subset of the RMI-IIOP and CORBA APIs to avoid a
|
||||
* static dependencies on the types defined by these APIs.
|
||||
*/
|
||||
|
||||
public interface IIOPProxy {
|
||||
|
||||
/**
|
||||
* Returns true if the given object is a Stub.
|
||||
*/
|
||||
boolean isStub(Object obj);
|
||||
|
||||
/**
|
||||
* Returns the Delegate to which the given Stub delegates.
|
||||
*/
|
||||
Object getDelegate(Object stub);
|
||||
|
||||
/**
|
||||
* Sets the Delegate for a given Stub.
|
||||
*/
|
||||
void setDelegate(Object stub, Object delegate);
|
||||
|
||||
/**
|
||||
* Returns the ORB associated with the given stub
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
* if the object does not support the operation that
|
||||
* was invoked
|
||||
*/
|
||||
Object getOrb(Object stub);
|
||||
|
||||
/**
|
||||
* Connects the Stub to the given ORB.
|
||||
*/
|
||||
void connect(Object stub, Object orb) throws RemoteException;
|
||||
|
||||
/**
|
||||
* Returns true if the given object is an ORB.
|
||||
*/
|
||||
boolean isOrb(Object obj);
|
||||
|
||||
/**
|
||||
* Creates, and returns, a new ORB instance.
|
||||
*/
|
||||
Object createOrb(String[] args, Properties props);
|
||||
|
||||
/**
|
||||
* Converts a string, produced by the object_to_string method, back
|
||||
* to a CORBA object reference.
|
||||
*/
|
||||
Object stringToObject(Object orb, String str);
|
||||
|
||||
/**
|
||||
* Converts the given CORBA object reference to a string.
|
||||
*/
|
||||
String objectToString(Object orb, Object obj);
|
||||
|
||||
/**
|
||||
* Checks to ensure that an object of a remote or abstract interface
|
||||
* type can be cast to a desired type.
|
||||
*/
|
||||
<T> T narrow(Object narrowFrom, Class<T> narrowTo);
|
||||
|
||||
/**
|
||||
* Makes a server object ready to receive remote calls
|
||||
*/
|
||||
void exportObject(Remote obj) throws RemoteException;
|
||||
|
||||
/**
|
||||
* Deregisters a server object from the runtime.
|
||||
*/
|
||||
void unexportObject(Remote obj) throws NoSuchObjectException;
|
||||
|
||||
/**
|
||||
* Returns a stub for the given server object.
|
||||
*/
|
||||
Remote toStub(Remote obj) throws NoSuchObjectException;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.management.remote.NotificationResult;
|
||||
import javax.management.remote.TargetedNotification;
|
||||
|
||||
/** A buffer of notifications received from an MBean server. */
|
||||
public interface NotificationBuffer {
|
||||
/**
|
||||
* <p>Fetch notifications that match the given listeners.</p>
|
||||
*
|
||||
* <p>The operation only considers notifications with a sequence
|
||||
* number at least <code>startSequenceNumber</code>. It will take
|
||||
* no longer than <code>timeout</code>, and will return no more
|
||||
* than <code>maxNotifications</code> different notifications.</p>
|
||||
*
|
||||
* <p>If there are no notifications matching the criteria, the
|
||||
* operation will block until one arrives, subject to the
|
||||
* timeout.</p>
|
||||
*
|
||||
* @param filter an object that will add notifications to a
|
||||
* {@code List<TargetedNotification>} if they match the current
|
||||
* listeners with their filters.
|
||||
* @param startSequenceNumber the first sequence number to
|
||||
* consider.
|
||||
* @param timeout the maximum time to wait. May be 0 to indicate
|
||||
* not to wait if there are no notifications.
|
||||
* @param maxNotifications the maximum number of notifications to
|
||||
* return. May be 0 to indicate a wait for eligible notifications
|
||||
* that will return a usable <code>nextSequenceNumber</code>. The
|
||||
* {@link TargetedNotification} array in the returned {@link
|
||||
* NotificationResult} may contain more than this number of
|
||||
* elements but will not contain more than this number of
|
||||
* different notifications.
|
||||
*/
|
||||
public NotificationResult
|
||||
fetchNotifications(NotificationBufferFilter filter,
|
||||
long startSequenceNumber,
|
||||
long timeout,
|
||||
int maxNotifications)
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* <p>Discard this buffer.</p>
|
||||
*/
|
||||
public void dispose();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.util.List;
|
||||
import javax.management.Notification;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.remote.TargetedNotification;
|
||||
|
||||
public interface NotificationBufferFilter {
|
||||
/**
|
||||
* Add the given notification coming from the given MBean to the list
|
||||
* iff it matches this filter's rules.
|
||||
*/
|
||||
public void apply(List<TargetedNotification> targetedNotifs,
|
||||
ObjectName source, Notification notif);
|
||||
}
|
||||
103
jdkSrc/jdk8/com/sun/jmx/remote/internal/ProxyRef.java
Normal file
103
jdkSrc/jdk8/com/sun/jmx/remote/internal/ProxyRef.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.lang.reflect.Method;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.server.RemoteObject;
|
||||
import java.rmi.server.RemoteRef;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ProxyRef implements RemoteRef {
|
||||
private static final long serialVersionUID = -6503061366316814723L;
|
||||
|
||||
public ProxyRef(RemoteRef ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public void readExternal(ObjectInput in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
ref.readExternal(in);
|
||||
}
|
||||
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
ref.writeExternal(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void invoke(java.rmi.server.RemoteCall call) throws Exception {
|
||||
ref.invoke(call);
|
||||
}
|
||||
|
||||
public Object invoke(Remote obj, Method method, Object[] params,
|
||||
long opnum) throws Exception {
|
||||
return ref.invoke(obj, method, params, opnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void done(java.rmi.server.RemoteCall call) throws RemoteException {
|
||||
ref.done(call);
|
||||
}
|
||||
|
||||
public String getRefClass(ObjectOutput out) {
|
||||
return ref.getRefClass(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public java.rmi.server.RemoteCall newCall(RemoteObject obj,
|
||||
java.rmi.server.Operation[] op, int opnum,
|
||||
long hash) throws RemoteException {
|
||||
return ref.newCall(obj, op, opnum, hash);
|
||||
}
|
||||
|
||||
public boolean remoteEquals(RemoteRef obj) {
|
||||
return ref.remoteEquals(obj);
|
||||
}
|
||||
|
||||
public int remoteHashCode() {
|
||||
return ref.remoteHashCode();
|
||||
}
|
||||
|
||||
public String remoteToString() {
|
||||
return ref.remoteToString();
|
||||
}
|
||||
|
||||
protected RemoteRef ref;
|
||||
}
|
||||
59
jdkSrc/jdk8/com/sun/jmx/remote/internal/RMIExporter.java
Normal file
59
jdkSrc/jdk8/com/sun/jmx/remote/internal/RMIExporter.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.rmi.NoSuchObjectException;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.server.RMIClientSocketFactory;
|
||||
import java.rmi.server.RMIServerSocketFactory;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
|
||||
/**
|
||||
* <p>Unpublished interface controlling how the RMI Connector Server
|
||||
* exports objects. The RMIServerImpl object and each
|
||||
* RMIConnectionImpl object are exported using the exporter. The
|
||||
* default exporter calls {@link
|
||||
* UnicastRemoteObject#exportObject(Remote, int,
|
||||
* RMIClientSocketFactory, RMIServerSocketFactory)} to export objects
|
||||
* and {@link UnicastRemoteObject#unexportObject(Remote, boolean)} to
|
||||
* unexport them. A replacement exporter can be specified via the
|
||||
* {@link #EXPORTER_ATTRIBUTE} property in the environment Map passed
|
||||
* to the RMI connector server.</p>
|
||||
*/
|
||||
public interface RMIExporter {
|
||||
public static final String EXPORTER_ATTRIBUTE =
|
||||
"com.sun.jmx.remote.rmi.exporter";
|
||||
|
||||
public Remote exportObject(Remote obj,
|
||||
int port,
|
||||
RMIClientSocketFactory csf,
|
||||
RMIServerSocketFactory ssf)
|
||||
throws RemoteException;
|
||||
|
||||
public boolean unexportObject(Remote obj, boolean force)
|
||||
throws NoSuchObjectException;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
|
||||
public abstract class ServerCommunicatorAdmin {
|
||||
public ServerCommunicatorAdmin(long timeout) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Constructor",
|
||||
"Creates a new ServerCommunicatorAdmin object "+
|
||||
"with the timeout "+timeout);
|
||||
}
|
||||
|
||||
this.timeout = timeout;
|
||||
|
||||
timestamp = 0;
|
||||
if (timeout < Long.MAX_VALUE) {
|
||||
Runnable timeoutTask = new Timeout();
|
||||
final Thread t = new Thread(timeoutTask);
|
||||
t.setName("JMX server connection timeout " + t.getId());
|
||||
// If you change this name you will need to change a unit test
|
||||
// (NoServerTimeoutTest)
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells that a new request message is received.
|
||||
* A caller of this method should always call the method
|
||||
* <code>rspOutgoing</code> to inform that a response is sent out
|
||||
* for the received request.
|
||||
* @return the value of the termination flag:
|
||||
* <ul><code>true</code> if the connection is already being terminated,
|
||||
* <br><code>false</code> otherwise.</ul>
|
||||
*/
|
||||
public boolean reqIncoming() {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("reqIncoming", "Receive a new request.");
|
||||
}
|
||||
|
||||
synchronized(lock) {
|
||||
if (terminated) {
|
||||
logger.warning("reqIncoming",
|
||||
"The server has decided to close " +
|
||||
"this client connection.");
|
||||
}
|
||||
++currentJobs;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells that a response is sent out for a received request.
|
||||
* @return the value of the termination flag:
|
||||
* <ul><code>true</code> if the connection is already being terminated,
|
||||
* <br><code>false</code> otherwise.</ul>
|
||||
*/
|
||||
public boolean rspOutgoing() {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("reqIncoming", "Finish a request.");
|
||||
}
|
||||
|
||||
synchronized(lock) {
|
||||
if (--currentJobs == 0) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
logtime("Admin: Timestamp=",timestamp);
|
||||
// tells the adminor to restart waiting with timeout
|
||||
lock.notify();
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by this class to tell an implementation to do stop.
|
||||
*/
|
||||
protected abstract void doStop();
|
||||
|
||||
/**
|
||||
* Terminates this object.
|
||||
* Called only by outside, so do not need to call doStop
|
||||
*/
|
||||
public void terminate() {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("terminate",
|
||||
"terminate the ServerCommunicatorAdmin object.");
|
||||
}
|
||||
|
||||
synchronized(lock) {
|
||||
if (terminated) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminated = true;
|
||||
|
||||
// tell Timeout to terminate
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// private classes
|
||||
// --------------------------------------------------------------
|
||||
private class Timeout implements Runnable {
|
||||
public void run() {
|
||||
boolean stopping = false;
|
||||
|
||||
synchronized(lock) {
|
||||
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
||||
logtime("Admin: timeout=",timeout);
|
||||
logtime("Admin: Timestamp=",timestamp);
|
||||
|
||||
while(!terminated) {
|
||||
try {
|
||||
// wait until there is no more job
|
||||
while(!terminated && currentJobs != 0) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Timeout-run",
|
||||
"Waiting without timeout.");
|
||||
}
|
||||
|
||||
lock.wait();
|
||||
}
|
||||
|
||||
if (terminated) return;
|
||||
|
||||
final long remaining =
|
||||
timeout - (System.currentTimeMillis() - timestamp);
|
||||
|
||||
logtime("Admin: remaining timeout=",remaining);
|
||||
|
||||
if (remaining > 0) {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Timeout-run",
|
||||
"Waiting with timeout: "+
|
||||
remaining + " ms remaining");
|
||||
}
|
||||
|
||||
lock.wait(remaining);
|
||||
}
|
||||
|
||||
if (currentJobs > 0) continue;
|
||||
|
||||
final long elapsed =
|
||||
System.currentTimeMillis() - timestamp;
|
||||
logtime("Admin: elapsed=",elapsed);
|
||||
|
||||
if (!terminated && elapsed > timeout) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Timeout-run",
|
||||
"timeout elapsed");
|
||||
}
|
||||
logtime("Admin: timeout elapsed! "+
|
||||
elapsed+">",timeout);
|
||||
// stopping
|
||||
terminated = true;
|
||||
|
||||
stopping = true;
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException ire) {
|
||||
logger.warning("Timeout-run","Unexpected Exception: "+
|
||||
ire);
|
||||
logger.debug("Timeout-run",ire);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stopping) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("Timeout-run", "Call the doStop.");
|
||||
}
|
||||
|
||||
doStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logtime(String desc,long time) {
|
||||
timelogger.trace("synchro",desc+time);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// private variables
|
||||
// --------------------------------------------------------------
|
||||
private long timestamp;
|
||||
|
||||
private final int[] lock = new int[0];
|
||||
private int currentJobs = 0;
|
||||
|
||||
private long timeout;
|
||||
|
||||
// state issue
|
||||
private boolean terminated = false;
|
||||
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc",
|
||||
"ServerCommunicatorAdmin");
|
||||
private static final ClassLogger timelogger =
|
||||
new ClassLogger("javax.management.remote.timeout",
|
||||
"ServerCommunicatorAdmin");
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import com.sun.jmx.remote.security.NotificationAccessController;
|
||||
import com.sun.jmx.remote.util.ClassLogger;
|
||||
import com.sun.jmx.remote.util.EnvHelp;
|
||||
import java.io.IOException;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.ListenerNotFoundException;
|
||||
import javax.management.MBeanPermission;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MBeanServerDelegate;
|
||||
import javax.management.MBeanServerNotification;
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationBroadcaster;
|
||||
import javax.management.NotificationFilter;
|
||||
import javax.management.ObjectInstance;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.remote.NotificationResult;
|
||||
import javax.management.remote.TargetedNotification;
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
public class ServerNotifForwarder {
|
||||
|
||||
|
||||
public ServerNotifForwarder(MBeanServer mbeanServer,
|
||||
Map<String, ?> env,
|
||||
NotificationBuffer notifBuffer,
|
||||
String connectionId) {
|
||||
this.mbeanServer = mbeanServer;
|
||||
this.notifBuffer = notifBuffer;
|
||||
this.connectionId = connectionId;
|
||||
connectionTimeout = EnvHelp.getServerConnectionTimeout(env);
|
||||
|
||||
String stringBoolean = (String) env.get("jmx.remote.x.check.notification.emission");
|
||||
checkNotificationEmission = EnvHelp.computeBooleanFromString( stringBoolean );
|
||||
notificationAccessController =
|
||||
EnvHelp.getNotificationAccessController(env);
|
||||
}
|
||||
|
||||
public Integer addNotificationListener(final ObjectName name,
|
||||
final NotificationFilter filter)
|
||||
throws InstanceNotFoundException, IOException {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("addNotificationListener",
|
||||
"Add a listener at " + name);
|
||||
}
|
||||
|
||||
checkState();
|
||||
|
||||
// Explicitly check MBeanPermission for addNotificationListener
|
||||
//
|
||||
checkMBeanPermission(name, "addNotificationListener");
|
||||
if (notificationAccessController != null) {
|
||||
notificationAccessController.addNotificationListener(
|
||||
connectionId, name, getSubject());
|
||||
}
|
||||
try {
|
||||
boolean instanceOf =
|
||||
AccessController.doPrivileged(
|
||||
new PrivilegedExceptionAction<Boolean>() {
|
||||
public Boolean run() throws InstanceNotFoundException {
|
||||
return mbeanServer.isInstanceOf(name, broadcasterClass);
|
||||
}
|
||||
});
|
||||
if (!instanceOf) {
|
||||
throw new IllegalArgumentException("The specified MBean [" +
|
||||
name + "] is not a " +
|
||||
"NotificationBroadcaster " +
|
||||
"object.");
|
||||
}
|
||||
} catch (PrivilegedActionException e) {
|
||||
throw (InstanceNotFoundException) extractException(e);
|
||||
}
|
||||
|
||||
final Integer id = getListenerID();
|
||||
|
||||
// 6238731: set the default domain if no domain is set.
|
||||
ObjectName nn = name;
|
||||
if (name.getDomain() == null || name.getDomain().equals("")) {
|
||||
try {
|
||||
nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(),
|
||||
name.getKeyPropertyList());
|
||||
} catch (MalformedObjectNameException mfoe) {
|
||||
// impossible, but...
|
||||
IOException ioe = new IOException(mfoe.getMessage());
|
||||
ioe.initCause(mfoe);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (listenerMap) {
|
||||
IdAndFilter idaf = new IdAndFilter(id, filter);
|
||||
Set<IdAndFilter> set = listenerMap.get(nn);
|
||||
// Tread carefully because if set.size() == 1 it may be the
|
||||
// Collections.singleton we make here, which is unmodifiable.
|
||||
if (set == null)
|
||||
set = Collections.singleton(idaf);
|
||||
else {
|
||||
if (set.size() == 1)
|
||||
set = new HashSet<IdAndFilter>(set);
|
||||
set.add(idaf);
|
||||
}
|
||||
listenerMap.put(nn, set);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public void removeNotificationListener(ObjectName name,
|
||||
Integer[] listenerIDs)
|
||||
throws Exception {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("removeNotificationListener",
|
||||
"Remove some listeners from " + name);
|
||||
}
|
||||
|
||||
checkState();
|
||||
|
||||
// Explicitly check MBeanPermission for removeNotificationListener
|
||||
//
|
||||
checkMBeanPermission(name, "removeNotificationListener");
|
||||
if (notificationAccessController != null) {
|
||||
notificationAccessController.removeNotificationListener(
|
||||
connectionId, name, getSubject());
|
||||
}
|
||||
|
||||
Exception re = null;
|
||||
for (int i = 0 ; i < listenerIDs.length ; i++) {
|
||||
try {
|
||||
removeNotificationListener(name, listenerIDs[i]);
|
||||
} catch (Exception e) {
|
||||
// Give back the first exception
|
||||
//
|
||||
if (re != null) {
|
||||
re = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (re != null) {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNotificationListener(ObjectName name, Integer listenerID)
|
||||
throws
|
||||
InstanceNotFoundException,
|
||||
ListenerNotFoundException,
|
||||
IOException {
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("removeNotificationListener",
|
||||
"Remove the listener " + listenerID + " from " + name);
|
||||
}
|
||||
|
||||
checkState();
|
||||
|
||||
if (name != null && !name.isPattern()) {
|
||||
if (!mbeanServer.isRegistered(name)) {
|
||||
throw new InstanceNotFoundException("The MBean " + name +
|
||||
" is not registered.");
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (listenerMap) {
|
||||
// Tread carefully because if set.size() == 1 it may be a
|
||||
// Collections.singleton, which is unmodifiable.
|
||||
Set<IdAndFilter> set = listenerMap.get(name);
|
||||
IdAndFilter idaf = new IdAndFilter(listenerID, null);
|
||||
if (set == null || !set.contains(idaf))
|
||||
throw new ListenerNotFoundException("Listener not found");
|
||||
if (set.size() == 1)
|
||||
listenerMap.remove(name);
|
||||
else
|
||||
set.remove(idaf);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is the object that will apply our filtering to candidate
|
||||
* notifications. First of all, if there are no listeners for the
|
||||
* ObjectName that the notification is coming from, we go no further.
|
||||
* Then, for each listener, we must apply the corresponding filter (if any)
|
||||
* and ignore the listener if the filter rejects. Finally, we apply
|
||||
* some access checks which may also reject the listener.
|
||||
*
|
||||
* A given notification may trigger several listeners on the same MBean,
|
||||
* which is why listenerMap is a Map<ObjectName, Set<IdAndFilter>> and
|
||||
* why we add the found notifications to a supplied List rather than
|
||||
* just returning a boolean.
|
||||
*/
|
||||
private final NotifForwarderBufferFilter bufferFilter = new NotifForwarderBufferFilter();
|
||||
|
||||
final class NotifForwarderBufferFilter implements NotificationBufferFilter {
|
||||
public void apply(List<TargetedNotification> targetedNotifs,
|
||||
ObjectName source, Notification notif) {
|
||||
// We proceed in two stages here, to avoid holding the listenerMap
|
||||
// lock while invoking the filters (which are user code).
|
||||
final IdAndFilter[] candidates;
|
||||
synchronized (listenerMap) {
|
||||
final Set<IdAndFilter> set = listenerMap.get(source);
|
||||
if (set == null) {
|
||||
logger.debug("bufferFilter", "no listeners for this name");
|
||||
return;
|
||||
}
|
||||
candidates = new IdAndFilter[set.size()];
|
||||
set.toArray(candidates);
|
||||
}
|
||||
// We don't synchronize on targetedNotifs, because it is a local
|
||||
// variable of our caller and no other thread can see it.
|
||||
for (IdAndFilter idaf : candidates) {
|
||||
final NotificationFilter nf = idaf.getFilter();
|
||||
if (nf == null || nf.isNotificationEnabled(notif)) {
|
||||
logger.debug("bufferFilter", "filter matches");
|
||||
final TargetedNotification tn =
|
||||
new TargetedNotification(notif, idaf.getId());
|
||||
if (allowNotificationEmission(source, tn))
|
||||
targetedNotifs.add(tn);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public NotificationResult fetchNotifs(long startSequenceNumber,
|
||||
long timeout,
|
||||
int maxNotifications) {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("fetchNotifs", "Fetching notifications, the " +
|
||||
"startSequenceNumber is " + startSequenceNumber +
|
||||
", the timeout is " + timeout +
|
||||
", the maxNotifications is " + maxNotifications);
|
||||
}
|
||||
|
||||
NotificationResult nr;
|
||||
final long t = Math.min(connectionTimeout, timeout);
|
||||
try {
|
||||
nr = notifBuffer.fetchNotifications(bufferFilter,
|
||||
startSequenceNumber,
|
||||
t, maxNotifications);
|
||||
snoopOnUnregister(nr);
|
||||
} catch (InterruptedException ire) {
|
||||
nr = new NotificationResult(0L, 0L, new TargetedNotification[0]);
|
||||
}
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("fetchNotifs", "Forwarding the notifs: "+nr);
|
||||
}
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
// The standard RMI connector client will register a listener on the MBeanServerDelegate
|
||||
// in order to be told when MBeans are unregistered. We snoop on fetched notifications
|
||||
// so that we can know too, and remove the corresponding entry from the listenerMap.
|
||||
// See 6957378.
|
||||
private void snoopOnUnregister(NotificationResult nr) {
|
||||
List<IdAndFilter> copy = null;
|
||||
synchronized (listenerMap) {
|
||||
Set<IdAndFilter> delegateSet = listenerMap.get(MBeanServerDelegate.DELEGATE_NAME);
|
||||
if (delegateSet == null || delegateSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
copy = new ArrayList<>(delegateSet);
|
||||
}
|
||||
|
||||
for (TargetedNotification tn : nr.getTargetedNotifications()) {
|
||||
Integer id = tn.getListenerID();
|
||||
for (IdAndFilter idaf : copy) {
|
||||
if (idaf.id == id) {
|
||||
// This is a notification from the MBeanServerDelegate.
|
||||
Notification n = tn.getNotification();
|
||||
if (n instanceof MBeanServerNotification &&
|
||||
n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
|
||||
MBeanServerNotification mbsn = (MBeanServerNotification) n;
|
||||
ObjectName gone = mbsn.getMBeanName();
|
||||
synchronized (listenerMap) {
|
||||
listenerMap.remove(gone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("terminate", "Be called.");
|
||||
}
|
||||
|
||||
synchronized(terminationLock) {
|
||||
if (terminated) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminated = true;
|
||||
|
||||
synchronized(listenerMap) {
|
||||
listenerMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.traceOn()) {
|
||||
logger.trace("terminate", "Terminated.");
|
||||
}
|
||||
}
|
||||
|
||||
//----------------
|
||||
// PRIVATE METHODS
|
||||
//----------------
|
||||
|
||||
private Subject getSubject() {
|
||||
return Subject.getSubject(AccessController.getContext());
|
||||
}
|
||||
|
||||
private void checkState() throws IOException {
|
||||
synchronized(terminationLock) {
|
||||
if (terminated) {
|
||||
throw new IOException("The connection has been terminated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getListenerID() {
|
||||
synchronized(listenerCounterLock) {
|
||||
return listenerCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly check the MBeanPermission for
|
||||
* the current access control context.
|
||||
*/
|
||||
public final void checkMBeanPermission(
|
||||
final ObjectName name, final String actions)
|
||||
throws InstanceNotFoundException, SecurityException {
|
||||
checkMBeanPermission(mbeanServer,name,actions);
|
||||
}
|
||||
|
||||
static void checkMBeanPermission(
|
||||
final MBeanServer mbs, final ObjectName name, final String actions)
|
||||
throws InstanceNotFoundException, SecurityException {
|
||||
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
AccessControlContext acc = AccessController.getContext();
|
||||
ObjectInstance oi;
|
||||
try {
|
||||
oi = AccessController.doPrivileged(
|
||||
new PrivilegedExceptionAction<ObjectInstance>() {
|
||||
public ObjectInstance run()
|
||||
throws InstanceNotFoundException {
|
||||
return mbs.getObjectInstance(name);
|
||||
}
|
||||
});
|
||||
} catch (PrivilegedActionException e) {
|
||||
throw (InstanceNotFoundException) extractException(e);
|
||||
}
|
||||
String classname = oi.getClassName();
|
||||
MBeanPermission perm = new MBeanPermission(
|
||||
classname,
|
||||
null,
|
||||
name,
|
||||
actions);
|
||||
sm.checkPermission(perm, acc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the caller has the right to get the following notifications.
|
||||
*/
|
||||
private boolean allowNotificationEmission(ObjectName name,
|
||||
TargetedNotification tn) {
|
||||
try {
|
||||
if (checkNotificationEmission) {
|
||||
checkMBeanPermission(name, "addNotificationListener");
|
||||
}
|
||||
if (notificationAccessController != null) {
|
||||
notificationAccessController.fetchNotification(
|
||||
connectionId, name, tn.getNotification(), getSubject());
|
||||
}
|
||||
return true;
|
||||
} catch (SecurityException e) {
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("fetchNotifs", "Notification " +
|
||||
tn.getNotification() + " not forwarded: the " +
|
||||
"caller didn't have the required access rights");
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
if (logger.debugOn()) {
|
||||
logger.debug("fetchNotifs", "Notification " +
|
||||
tn.getNotification() + " not forwarded: " +
|
||||
"got an unexpected exception: " + e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate until we extract the real exception
|
||||
* from a stack of PrivilegedActionExceptions.
|
||||
*/
|
||||
private static Exception extractException(Exception e) {
|
||||
while (e instanceof PrivilegedActionException) {
|
||||
e = ((PrivilegedActionException)e).getException();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private static class IdAndFilter {
|
||||
private Integer id;
|
||||
private NotificationFilter filter;
|
||||
|
||||
IdAndFilter(Integer id, NotificationFilter filter) {
|
||||
this.id = id;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
Integer getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
NotificationFilter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return ((o instanceof IdAndFilter) &&
|
||||
((IdAndFilter) o).getId().equals(getId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------
|
||||
// PRIVATE VARIABLES
|
||||
//------------------
|
||||
|
||||
private MBeanServer mbeanServer;
|
||||
|
||||
private final String connectionId;
|
||||
|
||||
private final long connectionTimeout;
|
||||
|
||||
private static int listenerCounter = 0;
|
||||
private final static int[] listenerCounterLock = new int[0];
|
||||
|
||||
private NotificationBuffer notifBuffer;
|
||||
private final Map<ObjectName, Set<IdAndFilter>> listenerMap =
|
||||
new HashMap<ObjectName, Set<IdAndFilter>>();
|
||||
|
||||
private boolean terminated = false;
|
||||
private final int[] terminationLock = new int[0];
|
||||
|
||||
static final String broadcasterClass =
|
||||
NotificationBroadcaster.class.getName();
|
||||
|
||||
private final boolean checkNotificationEmission;
|
||||
|
||||
private final NotificationAccessController notificationAccessController;
|
||||
|
||||
private static final ClassLogger logger =
|
||||
new ClassLogger("javax.management.remote.misc", "ServerNotifForwarder");
|
||||
}
|
||||
34
jdkSrc/jdk8/com/sun/jmx/remote/internal/Unmarshal.java
Normal file
34
jdkSrc/jdk8/com/sun/jmx/remote/internal/Unmarshal.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.jmx.remote.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.rmi.MarshalledObject;
|
||||
|
||||
public interface Unmarshal {
|
||||
public Object get(MarshalledObject<?> mo)
|
||||
throws IOException, ClassNotFoundException;
|
||||
}
|
||||
Reference in New Issue
Block a user