feat(jdk8): move files to new folder to avoid resources compiled.

This commit is contained in:
2025-09-07 15:25:52 +08:00
parent 3f0047bf6f
commit 8c35cfb1c0
17415 changed files with 217 additions and 213 deletions

View File

@@ -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();
}

View 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;
}

View File

@@ -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");
}

View 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;
}

View File

@@ -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");
}

View 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);
}
}

View 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;
}

View File

@@ -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();
}

View File

@@ -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);
}

View 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;
}

View 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;
}

View File

@@ -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");
}

View File

@@ -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");
}

View 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;
}

View File

@@ -0,0 +1,48 @@
/*
* 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.protocol.iiop;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.remote.JMXConnectorProvider;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
public class ClientProvider implements JMXConnectorProvider {
public JMXConnector newJMXConnector(JMXServiceURL serviceURL,
Map<String,?> environment)
throws IOException {
if (!serviceURL.getProtocol().equals("iiop")) {
throw new MalformedURLException("Protocol not iiop: " +
serviceURL.getProtocol());
}
return new RMIConnector(serviceURL, environment);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) 2009,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.protocol.iiop;
import org.omg.CORBA.ORB;
import org.omg.CORBA.portable.Delegate;
import javax.rmi.PortableRemoteObject;
import javax.rmi.CORBA.Stub;
import java.util.Properties;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import com.sun.jmx.remote.internal.IIOPProxy;
import java.io.SerializablePermission;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permissions;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
/**
* An implementation of IIOPProxy that simply delegates to the appropriate
* RMI-IIOP and CORBA APIs.
*/
public class IIOPProxyImpl implements IIOPProxy {
// special ACC used to initialize the IIOP stub
// the only allowed privilege is SerializablePermission("enableSubclassImplementation")
private static final AccessControlContext STUB_ACC;
static {
Permissions p = new Permissions();
p.add(new SerializablePermission("enableSubclassImplementation"));
STUB_ACC = new AccessControlContext(
new ProtectionDomain[]{
new ProtectionDomain(null, p)
}
);
}
public IIOPProxyImpl() { }
@Override
public boolean isStub(Object obj) {
return (obj instanceof Stub);
}
@Override
public Object getDelegate(Object stub) {
return ((Stub)stub)._get_delegate();
}
@Override
public void setDelegate(Object stub, Object delegate) {
((Stub)stub)._set_delegate((Delegate)delegate);
}
@Override
public Object getOrb(Object stub) {
try {
return ((Stub)stub)._orb();
} catch (org.omg.CORBA.BAD_OPERATION x) {
throw new UnsupportedOperationException(x);
}
}
@Override
public void connect(Object stub, Object orb)
throws RemoteException
{
((Stub)stub).connect((ORB)orb);
}
@Override
public boolean isOrb(Object obj) {
return (obj instanceof ORB);
}
@Override
public Object createOrb(String[] args, Properties props) {
return ORB.init(args, props);
}
@Override
public Object stringToObject(Object orb, String str) {
return ((ORB)orb).string_to_object(str);
}
@Override
public String objectToString(Object orb, Object obj) {
return ((ORB)orb).object_to_string((org.omg.CORBA.Object)obj);
}
@Override
@SuppressWarnings("unchecked")
public <T> T narrow(Object narrowFrom, Class<T> narrowTo) {
return (T)PortableRemoteObject.narrow(narrowFrom, narrowTo);
}
@Override
public void exportObject(Remote obj) throws RemoteException {
PortableRemoteObject.exportObject(obj);
}
@Override
public void unexportObject(Remote obj) throws NoSuchObjectException {
PortableRemoteObject.unexportObject(obj);
}
@Override
public Remote toStub(final Remote obj) throws NoSuchObjectException {
if (System.getSecurityManager() == null) {
return PortableRemoteObject.toStub(obj);
} else {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Remote>() {
@Override
public Remote run() throws Exception {
return PortableRemoteObject.toStub(obj);
}
}, STUB_ACC);
} catch (PrivilegedActionException e) {
if (e.getException() instanceof NoSuchObjectException) {
throw (NoSuchObjectException)e.getException();
}
throw new RuntimeException("Unexpected exception type", e.getException());
}
}
}
}

View File

@@ -0,0 +1,241 @@
/*
* 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.protocol.iiop;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import org.omg.CORBA.Any;
import org.omg.CORBA.Context;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.ORB;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.portable.BoxedValueHelper;
@SuppressWarnings({"deprecation", "rawtypes"})
public class ProxyInputStream extends org.omg.CORBA_2_3.portable.InputStream {
public ProxyInputStream(org.omg.CORBA.portable.InputStream in) {
this.in = in;
}
public boolean read_boolean() {
return in.read_boolean();
}
public char read_char() {
return in.read_char();
}
public char read_wchar() {
return in.read_wchar();
}
public byte read_octet() {
return in.read_octet();
}
public short read_short() {
return in.read_short();
}
public short read_ushort() {
return in.read_ushort();
}
public int read_long() {
return in.read_long();
}
public int read_ulong() {
return in.read_ulong();
}
public long read_longlong() {
return in.read_longlong();
}
public long read_ulonglong() {
return in.read_ulonglong();
}
public float read_float() {
return in.read_float();
}
public double read_double() {
return in.read_double();
}
public String read_string() {
return in.read_string();
}
public String read_wstring() {
return in.read_wstring();
}
public void read_boolean_array(boolean[] value, int offset, int length) {
in.read_boolean_array(value, offset, length);
}
public void read_char_array(char[] value, int offset, int length) {
in.read_char_array(value, offset, length);
}
public void read_wchar_array(char[] value, int offset, int length) {
in.read_wchar_array(value, offset, length);
}
public void read_octet_array(byte[] value, int offset, int length) {
in.read_octet_array(value, offset, length);
}
public void read_short_array(short[] value, int offset, int length) {
in.read_short_array(value, offset, length);
}
public void read_ushort_array(short[] value, int offset, int length) {
in.read_ushort_array(value, offset, length);
}
public void read_long_array(int[] value, int offset, int length) {
in.read_long_array(value, offset, length);
}
public void read_ulong_array(int[] value, int offset, int length) {
in.read_ulong_array(value, offset, length);
}
public void read_longlong_array(long[] value, int offset, int length) {
in.read_longlong_array(value, offset, length);
}
public void read_ulonglong_array(long[] value, int offset, int length) {
in.read_ulonglong_array(value, offset, length);
}
public void read_float_array(float[] value, int offset, int length) {
in.read_float_array(value, offset, length);
}
public void read_double_array(double[] value, int offset, int length) {
in.read_double_array(value, offset, length);
}
public org.omg.CORBA.Object read_Object() {
return in.read_Object();
}
public TypeCode read_TypeCode() {
return in.read_TypeCode();
}
public Any read_any() {
return in.read_any();
}
/**
* @deprecated
*/
@Override
@Deprecated
public org.omg.CORBA.Principal read_Principal() {
return in.read_Principal();
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public BigDecimal read_fixed() {
return in.read_fixed();
}
@Override
public Context read_Context() {
return in.read_Context();
}
@Override
public org.omg.CORBA.Object read_Object(java.lang.Class clz) {
return in.read_Object(clz);
}
@Override
public ORB orb() {
return in.orb();
}
@Override
public Serializable read_value() {
return narrow().read_value();
}
@Override
public Serializable read_value(Class clz) {
return narrow().read_value(clz);
}
@Override
public Serializable read_value(BoxedValueHelper factory) {
return narrow().read_value(factory);
}
@Override
public Serializable read_value(String rep_id) {
return narrow().read_value(rep_id);
}
@Override
public Serializable read_value(Serializable value) {
return narrow().read_value(value);
}
@Override
public Object read_abstract_interface() {
return narrow().read_abstract_interface();
}
@Override
public Object read_abstract_interface(Class clz) {
return narrow().read_abstract_interface(clz);
}
protected org.omg.CORBA_2_3.portable.InputStream narrow() {
if (in instanceof org.omg.CORBA_2_3.portable.InputStream)
return (org.omg.CORBA_2_3.portable.InputStream) in;
throw new NO_IMPLEMENT();
}
public org.omg.CORBA.portable.InputStream getProxiedInputStream() {
return in;
}
protected final org.omg.CORBA.portable.InputStream in;
}

View File

@@ -0,0 +1,51 @@
/*
* 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.protocol.iiop;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerProvider;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
public class ServerProvider implements JMXConnectorServerProvider {
public JMXConnectorServer newJMXConnectorServer(JMXServiceURL serviceURL,
Map<String,?> environment,
MBeanServer mbeanServer)
throws IOException {
if (!serviceURL.getProtocol().equals("iiop")) {
throw new MalformedURLException("Protocol not iiop: " +
serviceURL.getProtocol());
}
return new RMIConnectorServer(serviceURL, environment, mbeanServer);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2002, 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.protocol.rmi;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.remote.JMXConnectorProvider;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
public class ClientProvider implements JMXConnectorProvider {
public JMXConnector newJMXConnector(JMXServiceURL serviceURL,
Map<String,?> environment)
throws IOException {
if (!serviceURL.getProtocol().equals("rmi")) {
throw new MalformedURLException("Protocol not rmi: " +
serviceURL.getProtocol());
}
return new RMIConnector(serviceURL, environment);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.protocol.rmi;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerProvider;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
public class ServerProvider implements JMXConnectorServerProvider {
public JMXConnectorServer newJMXConnectorServer(JMXServiceURL serviceURL,
Map<String,?> environment,
MBeanServer mbeanServer)
throws IOException {
if (!serviceURL.getProtocol().equals("rmi")) {
throw new MalformedURLException("Protocol not rmi: " +
serviceURL.getProtocol());
}
return new RMIConnectorServer(serviceURL, environment, mbeanServer);
}
}

View File

@@ -0,0 +1,570 @@
/*
* Copyright (c) 2004, 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.security;
import com.sun.jmx.mbeanserver.GetPropertyAction;
import com.sun.jmx.mbeanserver.Util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import javax.management.remote.JMXPrincipal;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;
import sun.management.jmxremote.ConnectorBootstrap;
/**
* This {@link LoginModule} performs file-based authentication.
*
* <p> A supplied username and password is verified against the
* corresponding user credentials stored in a designated password file.
* If successful then a new {@link JMXPrincipal} is created with the
* user's name and it is associated with the current {@link Subject}.
* Such principals may be identified and granted management privileges in
* the access control file for JMX remote management or in a Java security
* policy.
*
* <p> The password file comprises a list of key-value pairs as specified in
* {@link Properties}. The key represents a user's name and the value is its
* associated cleartext password. By default, the following password file is
* used:
* <pre>
* ${java.home}/lib/management/jmxremote.password
* </pre>
* A different password file can be specified via the <code>passwordFile</code>
* configuration option.
*
* <p> This module recognizes the following <code>Configuration</code> options:
* <dl>
* <dt> <code>passwordFile</code> </dt>
* <dd> the path to an alternative password file. It is used instead of
* the default password file.</dd>
*
* <dt> <code>useFirstPass</code> </dt>
* <dd> if <code>true</code>, this module retrieves the username and password
* from the module's shared state, using "javax.security.auth.login.name"
* and "javax.security.auth.login.password" as the respective keys. The
* retrieved values are used for authentication. If authentication fails,
* no attempt for a retry is made, and the failure is reported back to
* the calling application.</dd>
*
* <dt> <code>tryFirstPass</code> </dt>
* <dd> if <code>true</code>, this module retrieves the username and password
* from the module's shared state, using "javax.security.auth.login.name"
* and "javax.security.auth.login.password" as the respective keys. The
* retrieved values are used for authentication. If authentication fails,
* the module uses the CallbackHandler to retrieve a new username and
* password, and another attempt to authenticate is made. If the
* authentication fails, the failure is reported back to the calling
* application.</dd>
*
* <dt> <code>storePass</code> </dt>
* <dd> if <code>true</code>, this module stores the username and password
* obtained from the CallbackHandler in the module's shared state, using
* "javax.security.auth.login.name" and
* "javax.security.auth.login.password" as the respective keys. This is
* not performed if existing values already exist for the username and
* password in the shared state, or if authentication fails.</dd>
*
* <dt> <code>clearPass</code> </dt>
* <dd> if <code>true</code>, this module clears the username and password
* stored in the module's shared state after both phases of authentication
* (login and commit) have completed.</dd>
* </dl>
*/
public class FileLoginModule implements LoginModule {
// Location of the default password file
private static final String DEFAULT_PASSWORD_FILE_NAME =
AccessController.doPrivileged(new GetPropertyAction("java.home")) +
File.separatorChar + "lib" +
File.separatorChar + "management" + File.separatorChar +
ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
// Key to retrieve the stored username
private static final String USERNAME_KEY =
"javax.security.auth.login.name";
// Key to retrieve the stored password
private static final String PASSWORD_KEY =
"javax.security.auth.login.password";
// Log messages
private static final ClassLogger logger =
new ClassLogger("javax.management.remote.misc", "FileLoginModule");
// Configurable options
private boolean useFirstPass = false;
private boolean tryFirstPass = false;
private boolean storePass = false;
private boolean clearPass = false;
// Authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// Supplied username and password
private String username;
private char[] password;
private JMXPrincipal user;
// Initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, Object> sharedState;
private Map<String, ?> options;
private String passwordFile;
private String passwordFileDisplayName;
private boolean userSuppliedPasswordFile;
private boolean hasJavaHomePermission;
private Properties userCredentials;
/**
* Initialize this <code>LoginModule</code>.
*
* @param subject the <code>Subject</code> to be authenticated.
* @param callbackHandler a <code>CallbackHandler</code> to acquire the
* user's name and password.
* @param sharedState shared <code>LoginModule</code> state.
* @param options options specified in the login
* <code>Configuration</code> for this particular
* <code>LoginModule</code>.
*/
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String,?> sharedState,
Map<String,?> options)
{
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = Util.cast(sharedState);
this.options = options;
// initialize any configured options
tryFirstPass =
"true".equalsIgnoreCase((String)options.get("tryFirstPass"));
useFirstPass =
"true".equalsIgnoreCase((String)options.get("useFirstPass"));
storePass =
"true".equalsIgnoreCase((String)options.get("storePass"));
clearPass =
"true".equalsIgnoreCase((String)options.get("clearPass"));
passwordFile = (String)options.get("passwordFile");
passwordFileDisplayName = passwordFile;
userSuppliedPasswordFile = true;
// set the location of the password file
if (passwordFile == null) {
passwordFile = DEFAULT_PASSWORD_FILE_NAME;
userSuppliedPasswordFile = false;
try {
System.getProperty("java.home");
hasJavaHomePermission = true;
passwordFileDisplayName = passwordFile;
} catch (SecurityException e) {
hasJavaHomePermission = false;
passwordFileDisplayName =
ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
}
}
}
/**
* Begin user authentication (Authentication Phase 1).
*
* <p> Acquire the user's name and password and verify them against
* the corresponding credentials from the password file.
*
* @return true always, since this <code>LoginModule</code>
* should not be ignored.
* @exception FailedLoginException if the authentication fails.
* @exception LoginException if this <code>LoginModule</code>
* is unable to perform the authentication.
*/
public boolean login() throws LoginException {
try {
loadPasswordFile();
} catch (IOException ioe) {
LoginException le = new LoginException(
"Error: unable to load the password file: " +
passwordFileDisplayName);
throw EnvHelp.initCause(le, ioe);
}
if (userCredentials == null) {
throw new LoginException
("Error: unable to locate the users' credentials.");
}
if (logger.debugOn()) {
logger.debug("login",
"Using password file: " + passwordFileDisplayName);
}
// attempt the authentication
if (tryFirstPass) {
try {
// attempt the authentication by getting the
// username and password from shared state
attemptAuthentication(true);
// authentication succeeded
succeeded = true;
if (logger.debugOn()) {
logger.debug("login",
"Authentication using cached password has succeeded");
}
return true;
} catch (LoginException le) {
// authentication failed -- try again below by prompting
cleanState();
logger.debug("login",
"Authentication using cached password has failed");
}
} else if (useFirstPass) {
try {
// attempt the authentication by getting the
// username and password from shared state
attemptAuthentication(true);
// authentication succeeded
succeeded = true;
if (logger.debugOn()) {
logger.debug("login",
"Authentication using cached password has succeeded");
}
return true;
} catch (LoginException le) {
// authentication failed
cleanState();
logger.debug("login",
"Authentication using cached password has failed");
throw le;
}
}
if (logger.debugOn()) {
logger.debug("login", "Acquiring password");
}
// attempt the authentication using the supplied username and password
try {
attemptAuthentication(false);
// authentication succeeded
succeeded = true;
if (logger.debugOn()) {
logger.debug("login", "Authentication has succeeded");
}
return true;
} catch (LoginException le) {
cleanState();
logger.debug("login", "Authentication has failed");
throw le;
}
}
/**
* Complete user authentication (Authentication Phase 2).
*
* <p> This method is called if the LoginContext's
* overall authentication has succeeded
* (all the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules have succeeded).
*
* <p> If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> method), then this method associates a
* <code>JMXPrincipal</code> with the <code>Subject</code> located in the
* <code>LoginModule</code>. If this LoginModule's own
* authentication attempted failed, then this method removes
* any state that was originally saved.
*
* @exception LoginException if the commit fails
* @return true if this LoginModule's own login and commit
* attempts succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
if (subject.isReadOnly()) {
cleanState();
throw new LoginException("Subject is read-only");
}
// add Principals to the Subject
if (!subject.getPrincipals().contains(user)) {
subject.getPrincipals().add(user);
}
if (logger.debugOn()) {
logger.debug("commit",
"Authentication has completed successfully");
}
}
// in any case, clean out state
cleanState();
commitSucceeded = true;
return true;
}
/**
* Abort user authentication (Authentication Phase 2).
*
* <p> This method is called if the LoginContext's overall authentication
* failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules did not succeed).
*
* <p> If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> and <code>commit</code> methods),
* then this method cleans up any state that was originally saved.
*
* @exception LoginException if the abort fails.
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (logger.debugOn()) {
logger.debug("abort",
"Authentication has not completed successfully");
}
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// Clean out state
succeeded = false;
cleanState();
user = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout a user.
*
* <p> This method removes the Principals
* that were added by the <code>commit</code> method.
*
* @exception LoginException if the logout fails.
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*/
public boolean logout() throws LoginException {
if (subject.isReadOnly()) {
cleanState();
throw new LoginException ("Subject is read-only");
}
subject.getPrincipals().remove(user);
// clean out state
cleanState();
succeeded = false;
commitSucceeded = false;
user = null;
if (logger.debugOn()) {
logger.debug("logout", "Subject is being logged out");
}
return true;
}
/**
* Attempt authentication
*
* @param usePasswdFromSharedState a flag to tell this method whether
* to retrieve the password from the sharedState.
*/
@SuppressWarnings("unchecked") // sharedState used as Map<String,Object>
private void attemptAuthentication(boolean usePasswdFromSharedState)
throws LoginException {
// get the username and password
getUsernamePassword(usePasswdFromSharedState);
String localPassword;
// userCredentials is initialized in login()
if (((localPassword = userCredentials.getProperty(username)) == null) ||
(! localPassword.equals(new String(password)))) {
// username not found or passwords do not match
if (logger.debugOn()) {
logger.debug("login", "Invalid username or password");
}
throw new FailedLoginException("Invalid username or password");
}
// Save the username and password in the shared state
// only if authentication succeeded
if (storePass &&
!sharedState.containsKey(USERNAME_KEY) &&
!sharedState.containsKey(PASSWORD_KEY)) {
sharedState.put(USERNAME_KEY, username);
sharedState.put(PASSWORD_KEY, password);
}
// Create a new user principal
user = new JMXPrincipal(username);
if (logger.debugOn()) {
logger.debug("login",
"User '" + username + "' successfully validated");
}
}
/*
* Read the password file.
*/
private void loadPasswordFile() throws IOException {
FileInputStream fis;
try {
fis = new FileInputStream(passwordFile);
} catch (SecurityException e) {
if (userSuppliedPasswordFile || hasJavaHomePermission) {
throw e;
} else {
final FilePermission fp =
new FilePermission(passwordFileDisplayName, "read");
AccessControlException ace = new AccessControlException(
"access denied " + fp.toString());
ace.setStackTrace(e.getStackTrace());
throw ace;
}
}
try {
final BufferedInputStream bis = new BufferedInputStream(fis);
try {
userCredentials = new Properties();
userCredentials.load(bis);
} finally {
bis.close();
}
} finally {
fis.close();
}
}
/**
* Get the username and password.
* This method does not return any value.
* Instead, it sets global name and password variables.
*
* <p> Also note that this method will set the username and password
* values in the shared state in case subsequent LoginModules
* want to use them via use/tryFirstPass.
*
* @param usePasswdFromSharedState boolean that tells this method whether
* to retrieve the password from the sharedState.
*/
private void getUsernamePassword(boolean usePasswdFromSharedState)
throws LoginException {
if (usePasswdFromSharedState) {
// use the password saved by the first module in the stack
username = (String)sharedState.get(USERNAME_KEY);
password = (char[])sharedState.get(PASSWORD_KEY);
return;
}
// acquire username and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("username");
callbacks[1] = new PasswordCallback("password", false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0,
password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
} catch (IOException ioe) {
LoginException le = new LoginException(ioe.toString());
throw EnvHelp.initCause(le, ioe);
} catch (UnsupportedCallbackException uce) {
LoginException le = new LoginException(
"Error: " + uce.getCallback().toString() +
" not available to garner authentication " +
"information from the user");
throw EnvHelp.initCause(le, uce);
}
}
/**
* Clean out state because of a failed authentication attempt
*/
private void cleanState() {
username = null;
if (password != null) {
Arrays.fill(password, ' ');
password = null;
}
if (clearPass) {
sharedState.remove(USERNAME_KEY);
sharedState.remove(PASSWORD_KEY);
}
}
}

View File

@@ -0,0 +1,346 @@
/*
* Copyright (c) 2004, 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.security;
import java.io.IOException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXAuthenticator;
import javax.security.auth.AuthPermission;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;
/**
* <p>This class represents a
* <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a>
* based implementation of the {@link JMXAuthenticator} interface.</p>
*
* <p>Authentication is performed by passing the supplied user's credentials
* to one or more authentication mechanisms ({@link LoginModule}) for
* verification. An authentication mechanism acquires the user's credentials
* by calling {@link NameCallback} and/or {@link PasswordCallback}.
* If authentication is successful then an authenticated {@link Subject}
* filled in with a {@link Principal} is returned. Authorization checks
* will then be performed based on this <code>Subject</code>.</p>
*
* <p>By default, a single file-based authentication mechanism
* {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p>
*
* <p>To override the default configuration use the
* <code>com.sun.management.jmxremote.login.config</code> management property
* described in the JRE/lib/management/management.properties file.
* Set this property to the name of a JAAS configuration entry and ensure that
* the entry is loaded by the installed {@link Configuration}. In addition,
* ensure that the authentication mechanisms specified in the entry acquire
* the user's credentials by calling {@link NameCallback} and
* {@link PasswordCallback} and that they return a {@link Subject} filled-in
* with a {@link Principal}, for those users that are successfully
* authenticated.</p>
*/
public final class JMXPluggableAuthenticator implements JMXAuthenticator {
/**
* Creates an instance of <code>JMXPluggableAuthenticator</code>
* and initializes it with a {@link LoginContext}.
*
* @param env the environment containing configuration properties for the
* authenticator. Can be null, which is equivalent to an empty
* Map.
* @exception SecurityException if the authentication mechanism cannot be
* initialized.
*/
public JMXPluggableAuthenticator(Map<?, ?> env) {
String loginConfigName = null;
String passwordFile = null;
if (env != null) {
loginConfigName = (String) env.get(LOGIN_CONFIG_PROP);
passwordFile = (String) env.get(PASSWORD_FILE_PROP);
}
try {
if (loginConfigName != null) {
// use the supplied JAAS login configuration
loginContext =
new LoginContext(loginConfigName, new JMXCallbackHandler());
} else {
// use the default JAAS login configuration (file-based)
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new AuthPermission("createLoginContext." +
LOGIN_CONFIG_NAME));
}
final String pf = passwordFile;
try {
loginContext = AccessController.doPrivileged(
new PrivilegedExceptionAction<LoginContext>() {
public LoginContext run() throws LoginException {
return new LoginContext(
LOGIN_CONFIG_NAME,
null,
new JMXCallbackHandler(),
new FileLoginConfig(pf));
}
});
} catch (PrivilegedActionException pae) {
throw (LoginException) pae.getException();
}
}
} catch (LoginException le) {
authenticationFailure("authenticate", le);
} catch (SecurityException se) {
authenticationFailure("authenticate", se);
}
}
/**
* Authenticate the <code>MBeanServerConnection</code> client
* with the given client credentials.
*
* @param credentials the user-defined credentials to be passed in
* to the server in order to authenticate the user before creating
* the <code>MBeanServerConnection</code>. This parameter must
* be a two-element <code>String[]</code> containing the client's
* username and password in that order.
*
* @return the authenticated subject containing a
* <code>JMXPrincipal(username)</code>.
*
* @exception SecurityException if the server cannot authenticate the user
* with the provided credentials.
*/
public Subject authenticate(Object credentials) {
// Verify that credentials is of type String[].
//
if (!(credentials instanceof String[])) {
// Special case for null so we get a more informative message
if (credentials == null)
authenticationFailure("authenticate", "Credentials required");
final String message =
"Credentials should be String[] instead of " +
credentials.getClass().getName();
authenticationFailure("authenticate", message);
}
// Verify that the array contains two elements.
//
final String[] aCredentials = (String[]) credentials;
if (aCredentials.length != 2) {
final String message =
"Credentials should have 2 elements not " +
aCredentials.length;
authenticationFailure("authenticate", message);
}
// Verify that username exists and the associated
// password matches the one supplied by the client.
//
username = aCredentials[0];
password = aCredentials[1];
if (username == null || password == null) {
final String message = "Username or password is null";
authenticationFailure("authenticate", message);
}
// Perform authentication
try {
loginContext.login();
final Subject subject = loginContext.getSubject();
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
subject.setReadOnly();
return null;
}
});
return subject;
} catch (LoginException le) {
authenticationFailure("authenticate", le);
}
return null;
}
private static void authenticationFailure(String method, String message)
throws SecurityException {
final String msg = "Authentication failed! " + message;
final SecurityException e = new SecurityException(msg);
logException(method, msg, e);
throw e;
}
private static void authenticationFailure(String method,
Exception exception)
throws SecurityException {
String msg;
SecurityException se;
if (exception instanceof SecurityException) {
msg = exception.getMessage();
se = (SecurityException) exception;
} else {
msg = "Authentication failed! " + exception.getMessage();
final SecurityException e = new SecurityException(msg);
EnvHelp.initCause(e, exception);
se = e;
}
logException(method, msg, se);
throw se;
}
private static void logException(String method,
String message,
Exception e) {
if (logger.traceOn()) {
logger.trace(method, message);
}
if (logger.debugOn()) {
logger.debug(method, e);
}
}
private LoginContext loginContext;
private String username;
private String password;
private static final String LOGIN_CONFIG_PROP =
"jmx.remote.x.login.config";
private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator";
private static final String PASSWORD_FILE_PROP =
"jmx.remote.x.password.file";
private static final ClassLogger logger =
new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME);
/**
* This callback handler supplies the username and password (which was
* originally supplied by the JMX user) to the JAAS login module performing
* the authentication. No interactive user prompting is required because the
* credentials are already available to this class (via its enclosing class).
*/
private final class JMXCallbackHandler implements CallbackHandler {
/**
* Sets the username and password in the appropriate Callback object.
*/
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback)callbacks[i]).setName(username);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback)callbacks[i])
.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
/**
* This class defines the JAAS configuration for file-based authentication.
* It is equivalent to the following textual configuration entry:
* <pre>
* JMXPluggableAuthenticator {
* com.sun.jmx.remote.security.FileLoginModule required;
* };
* </pre>
*/
private static class FileLoginConfig extends Configuration {
// The JAAS configuration for file-based authentication
private AppConfigurationEntry[] entries;
// The classname of the login module for file-based authentication
private static final String FILE_LOGIN_MODULE =
FileLoginModule.class.getName();
// The option that identifies the password file to use
private static final String PASSWORD_FILE_OPTION = "passwordFile";
/**
* Creates an instance of <code>FileLoginConfig</code>
*
* @param passwordFile A filepath that identifies the password file to use.
* If null then the default password file is used.
*/
public FileLoginConfig(String passwordFile) {
Map<String, String> options;
if (passwordFile != null) {
options = new HashMap<String, String>(1);
options.put(PASSWORD_FILE_OPTION, passwordFile);
} else {
options = Collections.emptyMap();
}
entries = new AppConfigurationEntry[] {
new AppConfigurationEntry(FILE_LOGIN_MODULE,
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options)
};
}
/**
* Gets the JAAS configuration for file-based authentication
*/
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return name.equals(LOGIN_CONFIG_NAME) ? entries : null;
}
/**
* Refreshes the configuration.
*/
public void refresh() {
// the configuration is fixed
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2003, 2005, 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.security;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.ProtectionDomain;
import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
/**
* <p>This class represents an extension to the {@link SubjectDomainCombiner}
* and is used to add a new {@link ProtectionDomain}, comprised of a null
* codesource/signers and an empty permission set, to the access control
* context with which this combiner is combined.</p>
*
* <p>When the {@link #combine} method is called the {@link ProtectionDomain}
* is augmented with the permissions granted to the set of principals present
* in the supplied {@link Subject}.</p>
*/
public class JMXSubjectDomainCombiner extends SubjectDomainCombiner {
public JMXSubjectDomainCombiner(Subject s) {
super(s);
}
public ProtectionDomain[] combine(ProtectionDomain[] current,
ProtectionDomain[] assigned) {
// Add a new ProtectionDomain with the null codesource/signers, and
// the empty permission set, to the end of the array containing the
// 'current' protections domains, i.e. the ones that will be augmented
// with the permissions granted to the set of principals present in
// the supplied subject.
//
ProtectionDomain[] newCurrent;
if (current == null || current.length == 0) {
newCurrent = new ProtectionDomain[1];
newCurrent[0] = pdNoPerms;
} else {
newCurrent = new ProtectionDomain[current.length + 1];
for (int i = 0; i < current.length; i++) {
newCurrent[i] = current[i];
}
newCurrent[current.length] = pdNoPerms;
}
return super.combine(newCurrent, assigned);
}
/**
* A null CodeSource.
*/
private static final CodeSource nullCodeSource =
new CodeSource(null, (java.security.cert.Certificate[]) null);
/**
* A ProtectionDomain with a null CodeSource and an empty permission set.
*/
private static final ProtectionDomain pdNoPerms =
new ProtectionDomain(nullCodeSource, new Permissions(), null, null);
/**
* Get the current AccessControlContext combined with the supplied subject.
*/
public static AccessControlContext getContext(Subject subject) {
return new AccessControlContext(AccessController.getContext(),
new JMXSubjectDomainCombiner(subject));
}
/**
* Get the AccessControlContext of the domain combiner created with
* the supplied subject, i.e. an AccessControlContext with the domain
* combiner created with the supplied subject and where the caller's
* context has been removed.
*/
public static AccessControlContext
getDomainCombinerContext(Subject subject) {
return new AccessControlContext(
new AccessControlContext(new ProtectionDomain[0]),
new JMXSubjectDomainCombiner(subject));
}
}

View File

@@ -0,0 +1,665 @@
/*
* 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.security;
import com.sun.jmx.mbeanserver.GetPropertyAction;
import java.io.ObjectInputStream;
import java.security.AccessController;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.loading.ClassLoaderRepository;
import javax.management.remote.MBeanServerForwarder;
/**
* <p>An object of this class implements the MBeanServer interface
* and, for each of its methods, calls an appropriate checking method
* and then forwards the request to a wrapped MBeanServer object. The
* checking method may throw a RuntimeException if the operation is
* not allowed; in this case the request is not forwarded to the
* wrapped object.</p>
*
* <p>A typical use of this class is to insert it between a connector server
* such as the RMI connector and the MBeanServer with which the connector
* is associated. Requests from the connector client can then be filtered
* and those operations that are not allowed, or not allowed in a particular
* context, can be rejected by throwing a <code>SecurityException</code>
* in the corresponding <code>check*</code> method.</p>
*
* <p>This is an abstract class, because in its implementation none of
* the checking methods does anything. To be useful, it must be
* subclassed and at least one of the checking methods overridden to
* do some checking. Some or all of the MBeanServer methods may also
* be overridden, for instance if the default checking behavior is
* inappropriate.</p>
*
* <p>If there is no SecurityManager, then the access controller will refuse
* to create an MBean that is a ClassLoader, which includes MLets, or to
* execute the method addURL on an MBean that is an MLet. This prevents
* people from opening security holes unintentionally. Otherwise, it
* would not be obvious that granting write access grants the ability to
* download and execute arbitrary code in the target MBean server. Advanced
* users who do want the ability to use MLets are presumably advanced enough
* to handle policy files and security managers.</p>
*/
public abstract class MBeanServerAccessController
implements MBeanServerForwarder {
public MBeanServer getMBeanServer() {
return mbs;
}
public void setMBeanServer(MBeanServer mbs) {
if (mbs == null)
throw new IllegalArgumentException("Null MBeanServer");
if (this.mbs != null)
throw new IllegalArgumentException("MBeanServer object already " +
"initialized");
this.mbs = mbs;
}
/**
* Check if the caller can do read operations. This method does
* nothing if so, otherwise throws SecurityException.
*/
protected abstract void checkRead();
/**
* Check if the caller can do write operations. This method does
* nothing if so, otherwise throws SecurityException.
*/
protected abstract void checkWrite();
/**
* Check if the caller can create the named class. The default
* implementation of this method calls {@link #checkWrite()}.
*/
protected void checkCreate(String className) {
checkWrite();
}
/**
* Check if the caller can unregister the named MBean. The default
* implementation of this method calls {@link #checkWrite()}.
*/
protected void checkUnregister(ObjectName name) {
checkWrite();
}
//--------------------------------------------
//--------------------------------------------
//
// Implementation of the MBeanServer interface
//
//--------------------------------------------
//--------------------------------------------
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void addNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException {
checkRead();
getMBeanServer().addNotificationListener(name, listener,
filter, handback);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void addNotificationListener(ObjectName name,
ObjectName listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException {
checkRead();
getMBeanServer().addNotificationListener(name, listener,
filter, handback);
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance createMBean(String className, ObjectName name)
throws
ReflectionException,
InstanceAlreadyExistsException,
MBeanRegistrationException,
MBeanException,
NotCompliantMBeanException {
checkCreate(className);
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
Object object = getMBeanServer().instantiate(className);
checkClassLoader(object);
return getMBeanServer().registerMBean(object, name);
} else {
return getMBeanServer().createMBean(className, name);
}
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance createMBean(String className, ObjectName name,
Object params[], String signature[])
throws
ReflectionException,
InstanceAlreadyExistsException,
MBeanRegistrationException,
MBeanException,
NotCompliantMBeanException {
checkCreate(className);
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
Object object = getMBeanServer().instantiate(className,
params,
signature);
checkClassLoader(object);
return getMBeanServer().registerMBean(object, name);
} else {
return getMBeanServer().createMBean(className, name,
params, signature);
}
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance createMBean(String className,
ObjectName name,
ObjectName loaderName)
throws
ReflectionException,
InstanceAlreadyExistsException,
MBeanRegistrationException,
MBeanException,
NotCompliantMBeanException,
InstanceNotFoundException {
checkCreate(className);
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
Object object = getMBeanServer().instantiate(className,
loaderName);
checkClassLoader(object);
return getMBeanServer().registerMBean(object, name);
} else {
return getMBeanServer().createMBean(className, name, loaderName);
}
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance createMBean(String className,
ObjectName name,
ObjectName loaderName,
Object params[],
String signature[])
throws
ReflectionException,
InstanceAlreadyExistsException,
MBeanRegistrationException,
MBeanException,
NotCompliantMBeanException,
InstanceNotFoundException {
checkCreate(className);
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
Object object = getMBeanServer().instantiate(className,
loaderName,
params,
signature);
checkClassLoader(object);
return getMBeanServer().registerMBean(object, name);
} else {
return getMBeanServer().createMBean(className, name, loaderName,
params, signature);
}
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
@Deprecated
public ObjectInputStream deserialize(ObjectName name, byte[] data)
throws InstanceNotFoundException, OperationsException {
checkRead();
return getMBeanServer().deserialize(name, data);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
@Deprecated
public ObjectInputStream deserialize(String className, byte[] data)
throws OperationsException, ReflectionException {
checkRead();
return getMBeanServer().deserialize(className, data);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
@Deprecated
public ObjectInputStream deserialize(String className,
ObjectName loaderName,
byte[] data)
throws
InstanceNotFoundException,
OperationsException,
ReflectionException {
checkRead();
return getMBeanServer().deserialize(className, loaderName, data);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public Object getAttribute(ObjectName name, String attribute)
throws
MBeanException,
AttributeNotFoundException,
InstanceNotFoundException,
ReflectionException {
checkRead();
return getMBeanServer().getAttribute(name, attribute);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public AttributeList getAttributes(ObjectName name, String[] attributes)
throws InstanceNotFoundException, ReflectionException {
checkRead();
return getMBeanServer().getAttributes(name, attributes);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public ClassLoader getClassLoader(ObjectName loaderName)
throws InstanceNotFoundException {
checkRead();
return getMBeanServer().getClassLoader(loaderName);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public ClassLoader getClassLoaderFor(ObjectName mbeanName)
throws InstanceNotFoundException {
checkRead();
return getMBeanServer().getClassLoaderFor(mbeanName);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public ClassLoaderRepository getClassLoaderRepository() {
checkRead();
return getMBeanServer().getClassLoaderRepository();
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public String getDefaultDomain() {
checkRead();
return getMBeanServer().getDefaultDomain();
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public String[] getDomains() {
checkRead();
return getMBeanServer().getDomains();
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public Integer getMBeanCount() {
checkRead();
return getMBeanServer().getMBeanCount();
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public MBeanInfo getMBeanInfo(ObjectName name)
throws
InstanceNotFoundException,
IntrospectionException,
ReflectionException {
checkRead();
return getMBeanServer().getMBeanInfo(name);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance getObjectInstance(ObjectName name)
throws InstanceNotFoundException {
checkRead();
return getMBeanServer().getObjectInstance(name);
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public Object instantiate(String className)
throws ReflectionException, MBeanException {
checkCreate(className);
return getMBeanServer().instantiate(className);
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public Object instantiate(String className,
Object params[],
String signature[])
throws ReflectionException, MBeanException {
checkCreate(className);
return getMBeanServer().instantiate(className, params, signature);
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public Object instantiate(String className, ObjectName loaderName)
throws ReflectionException, MBeanException, InstanceNotFoundException {
checkCreate(className);
return getMBeanServer().instantiate(className, loaderName);
}
/**
* Call <code>checkCreate(className)</code>, then forward this method to the
* wrapped object.
*/
public Object instantiate(String className, ObjectName loaderName,
Object params[], String signature[])
throws ReflectionException, MBeanException, InstanceNotFoundException {
checkCreate(className);
return getMBeanServer().instantiate(className, loaderName,
params, signature);
}
/**
* Call <code>checkWrite()</code>, then forward this method to the
* wrapped object.
*/
public Object invoke(ObjectName name, String operationName,
Object params[], String signature[])
throws
InstanceNotFoundException,
MBeanException,
ReflectionException {
checkWrite();
checkMLetMethods(name, operationName);
return getMBeanServer().invoke(name, operationName, params, signature);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public boolean isInstanceOf(ObjectName name, String className)
throws InstanceNotFoundException {
checkRead();
return getMBeanServer().isInstanceOf(name, className);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public boolean isRegistered(ObjectName name) {
checkRead();
return getMBeanServer().isRegistered(name);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
checkRead();
return getMBeanServer().queryMBeans(name, query);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
checkRead();
return getMBeanServer().queryNames(name, query);
}
/**
* Call <code>checkWrite()</code>, then forward this method to the
* wrapped object.
*/
public ObjectInstance registerMBean(Object object, ObjectName name)
throws
InstanceAlreadyExistsException,
MBeanRegistrationException,
NotCompliantMBeanException {
checkWrite();
return getMBeanServer().registerMBean(object, name);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void removeNotificationListener(ObjectName name,
NotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException {
checkRead();
getMBeanServer().removeNotificationListener(name, listener);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void removeNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
checkRead();
getMBeanServer().removeNotificationListener(name, listener,
filter, handback);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void removeNotificationListener(ObjectName name,
ObjectName listener)
throws InstanceNotFoundException, ListenerNotFoundException {
checkRead();
getMBeanServer().removeNotificationListener(name, listener);
}
/**
* Call <code>checkRead()</code>, then forward this method to the
* wrapped object.
*/
public void removeNotificationListener(ObjectName name,
ObjectName listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
checkRead();
getMBeanServer().removeNotificationListener(name, listener,
filter, handback);
}
/**
* Call <code>checkWrite()</code>, then forward this method to the
* wrapped object.
*/
public void setAttribute(ObjectName name, Attribute attribute)
throws
InstanceNotFoundException,
AttributeNotFoundException,
InvalidAttributeValueException,
MBeanException,
ReflectionException {
checkWrite();
getMBeanServer().setAttribute(name, attribute);
}
/**
* Call <code>checkWrite()</code>, then forward this method to the
* wrapped object.
*/
public AttributeList setAttributes(ObjectName name,
AttributeList attributes)
throws InstanceNotFoundException, ReflectionException {
checkWrite();
return getMBeanServer().setAttributes(name, attributes);
}
/**
* Call <code>checkUnregister()</code>, then forward this method to the
* wrapped object.
*/
public void unregisterMBean(ObjectName name)
throws InstanceNotFoundException, MBeanRegistrationException {
checkUnregister(name);
getMBeanServer().unregisterMBean(name);
}
//----------------
// PRIVATE METHODS
//----------------
private void checkClassLoader(Object object) {
if (object instanceof ClassLoader)
throw new SecurityException("Access denied! Creating an " +
"MBean that is a ClassLoader " +
"is forbidden unless a security " +
"manager is installed.");
}
private void checkMLetMethods(ObjectName name, String operation)
throws InstanceNotFoundException {
// Check if security manager installed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
return;
}
// Check for addURL and getMBeansFromURL methods
if (!operation.equals("addURL") &&
!operation.equals("getMBeansFromURL")) {
return;
}
// Check if MBean is instance of MLet
if (!getMBeanServer().isInstanceOf(name,
"javax.management.loading.MLet")) {
return;
}
// Throw security exception
if (operation.equals("addURL")) { // addURL
throw new SecurityException("Access denied! MLet method addURL " +
"cannot be invoked unless a security manager is installed.");
} else { // getMBeansFromURL
// Whether or not calling getMBeansFromURL is allowed is controlled
// by the value of the "jmx.remote.x.mlet.allow.getMBeansFromURL"
// system property. If the value of this property is true, calling
// the MLet's getMBeansFromURL method is allowed. The default value
// for this property is false.
final String propName = "jmx.remote.x.mlet.allow.getMBeansFromURL";
GetPropertyAction propAction = new GetPropertyAction(propName);
String propValue = AccessController.doPrivileged(propAction);
boolean allowGetMBeansFromURL = "true".equalsIgnoreCase(propValue);
if (!allowGetMBeansFromURL) {
throw new SecurityException("Access denied! MLet method " +
"getMBeansFromURL cannot be invoked unless a " +
"security manager is installed or the system property " +
"-Djmx.remote.x.mlet.allow.getMBeansFromURL=true " +
"is specified.");
}
}
}
//------------------
// PRIVATE VARIABLES
//------------------
private MBeanServer mbs;
}

View File

@@ -0,0 +1,544 @@
/*
* 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.security;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.security.auth.Subject;
/**
* <p>An object of this class implements the MBeanServerAccessController
* interface and, for each of its methods, calls an appropriate checking
* method and then forwards the request to a wrapped MBeanServer object.
* The checking method may throw a SecurityException if the operation is
* not allowed; in this case the request is not forwarded to the
* wrapped object.</p>
*
* <p>This class implements the {@link #checkRead()}, {@link #checkWrite()},
* {@link #checkCreate(String)}, and {@link #checkUnregister(ObjectName)}
* methods based on an access level properties file containing username/access
* level pairs. The set of username/access level pairs is passed either as a
* filename which denotes a properties file on disk, or directly as an instance
* of the {@link Properties} class. In both cases, the name of each property
* represents a username, and the value of the property is the associated access
* level. Thus, any given username either does not exist in the properties or
* has exactly one access level. The same access level can be shared by several
* usernames.</p>
*
* <p>The supported access level values are {@code readonly} and
* {@code readwrite}. The {@code readwrite} access level can be
* qualified by one or more <i>clauses</i>, where each clause looks
* like <code>create <i>classNamePattern</i></code> or {@code
* unregister}. For example:</p>
*
* <pre>
* monitorRole readonly
* controlRole readwrite \
* create javax.management.timer.*,javax.management.monitor.* \
* unregister
* </pre>
*
* <p>(The continuation lines with {@code \} come from the parser for
* Properties files.)</p>
*/
public class MBeanServerFileAccessController
extends MBeanServerAccessController {
static final String READONLY = "readonly";
static final String READWRITE = "readwrite";
static final String CREATE = "create";
static final String UNREGISTER = "unregister";
private enum AccessType {READ, WRITE, CREATE, UNREGISTER};
private static class Access {
final boolean write;
final String[] createPatterns;
private boolean unregister;
Access(boolean write, boolean unregister, List<String> createPatternList) {
this.write = write;
int npats = (createPatternList == null) ? 0 : createPatternList.size();
if (npats == 0)
this.createPatterns = NO_STRINGS;
else
this.createPatterns = createPatternList.toArray(new String[npats]);
this.unregister = unregister;
}
private final String[] NO_STRINGS = new String[0];
}
/**
* <p>Create a new MBeanServerAccessController that forwards all the
* MBeanServer requests to the MBeanServer set by invoking the {@link
* #setMBeanServer} method after doing access checks based on read and
* write permissions.</p>
*
* <p>This instance is initialized from the specified properties file.</p>
*
* @param accessFileName name of the file which denotes a properties
* file on disk containing the username/access level entries.
*
* @exception IOException if the file does not exist, is a
* directory rather than a regular file, or for some other
* reason cannot be opened for reading.
*
* @exception IllegalArgumentException if any of the supplied access
* level values differs from "readonly" or "readwrite".
*/
public MBeanServerFileAccessController(String accessFileName)
throws IOException {
super();
this.accessFileName = accessFileName;
Properties props = propertiesFromFile(accessFileName);
parseProperties(props);
}
/**
* <p>Create a new MBeanServerAccessController that forwards all the
* MBeanServer requests to <code>mbs</code> after doing access checks
* based on read and write permissions.</p>
*
* <p>This instance is initialized from the specified properties file.</p>
*
* @param accessFileName name of the file which denotes a properties
* file on disk containing the username/access level entries.
*
* @param mbs the MBeanServer object to which requests will be forwarded.
*
* @exception IOException if the file does not exist, is a
* directory rather than a regular file, or for some other
* reason cannot be opened for reading.
*
* @exception IllegalArgumentException if any of the supplied access
* level values differs from "readonly" or "readwrite".
*/
public MBeanServerFileAccessController(String accessFileName,
MBeanServer mbs)
throws IOException {
this(accessFileName);
setMBeanServer(mbs);
}
/**
* <p>Create a new MBeanServerAccessController that forwards all the
* MBeanServer requests to the MBeanServer set by invoking the {@link
* #setMBeanServer} method after doing access checks based on read and
* write permissions.</p>
*
* <p>This instance is initialized from the specified properties
* instance. This constructor makes a copy of the properties
* instance and it is the copy that is consulted to check the
* username and access level of an incoming connection. The
* original properties object can be modified without affecting
* the copy. If the {@link #refresh} method is then called, the
* <code>MBeanServerFileAccessController</code> will make a new
* copy of the properties object at that time.</p>
*
* @param accessFileProps properties list containing the username/access
* level entries.
*
* @exception IllegalArgumentException if <code>accessFileProps</code> is
* <code>null</code> or if any of the supplied access level values differs
* from "readonly" or "readwrite".
*/
public MBeanServerFileAccessController(Properties accessFileProps)
throws IOException {
super();
if (accessFileProps == null)
throw new IllegalArgumentException("Null properties");
originalProps = accessFileProps;
parseProperties(accessFileProps);
}
/**
* <p>Create a new MBeanServerAccessController that forwards all the
* MBeanServer requests to the MBeanServer set by invoking the {@link
* #setMBeanServer} method after doing access checks based on read and
* write permissions.</p>
*
* <p>This instance is initialized from the specified properties
* instance. This constructor makes a copy of the properties
* instance and it is the copy that is consulted to check the
* username and access level of an incoming connection. The
* original properties object can be modified without affecting
* the copy. If the {@link #refresh} method is then called, the
* <code>MBeanServerFileAccessController</code> will make a new
* copy of the properties object at that time.</p>
*
* @param accessFileProps properties list containing the username/access
* level entries.
*
* @param mbs the MBeanServer object to which requests will be forwarded.
*
* @exception IllegalArgumentException if <code>accessFileProps</code> is
* <code>null</code> or if any of the supplied access level values differs
* from "readonly" or "readwrite".
*/
public MBeanServerFileAccessController(Properties accessFileProps,
MBeanServer mbs)
throws IOException {
this(accessFileProps);
setMBeanServer(mbs);
}
/**
* Check if the caller can do read operations. This method does
* nothing if so, otherwise throws SecurityException.
*/
@Override
public void checkRead() {
checkAccess(AccessType.READ, null);
}
/**
* Check if the caller can do write operations. This method does
* nothing if so, otherwise throws SecurityException.
*/
@Override
public void checkWrite() {
checkAccess(AccessType.WRITE, null);
}
/**
* Check if the caller can create MBeans or instances of the given class.
* This method does nothing if so, otherwise throws SecurityException.
*/
@Override
public void checkCreate(String className) {
checkAccess(AccessType.CREATE, className);
}
/**
* Check if the caller can do unregister operations. This method does
* nothing if so, otherwise throws SecurityException.
*/
@Override
public void checkUnregister(ObjectName name) {
checkAccess(AccessType.UNREGISTER, null);
}
/**
* <p>Refresh the set of username/access level entries.</p>
*
* <p>If this instance was created using the
* {@link #MBeanServerFileAccessController(String)} or
* {@link #MBeanServerFileAccessController(String,MBeanServer)}
* constructors to specify a file from which the entries are read,
* the file is re-read.</p>
*
* <p>If this instance was created using the
* {@link #MBeanServerFileAccessController(Properties)} or
* {@link #MBeanServerFileAccessController(Properties,MBeanServer)}
* constructors then a new copy of the <code>Properties</code> object
* is made.</p>
*
* @exception IOException if the file does not exist, is a
* directory rather than a regular file, or for some other
* reason cannot be opened for reading.
*
* @exception IllegalArgumentException if any of the supplied access
* level values differs from "readonly" or "readwrite".
*/
public synchronized void refresh() throws IOException {
Properties props;
if (accessFileName == null)
props = (Properties) originalProps;
else
props = propertiesFromFile(accessFileName);
parseProperties(props);
}
private static Properties propertiesFromFile(String fname)
throws IOException {
FileInputStream fin = new FileInputStream(fname);
try {
Properties p = new Properties();
p.load(fin);
return p;
} finally {
fin.close();
}
}
private synchronized void checkAccess(AccessType requiredAccess, String arg) {
final AccessControlContext acc = AccessController.getContext();
final Subject s =
AccessController.doPrivileged(new PrivilegedAction<Subject>() {
public Subject run() {
return Subject.getSubject(acc);
}
});
if (s == null) return; /* security has not been enabled */
final Set principals = s.getPrincipals();
String newPropertyValue = null;
for (Iterator i = principals.iterator(); i.hasNext(); ) {
final Principal p = (Principal) i.next();
Access access = accessMap.get(p.getName());
if (access != null) {
boolean ok;
switch (requiredAccess) {
case READ:
ok = true; // all access entries imply read
break;
case WRITE:
ok = access.write;
break;
case UNREGISTER:
ok = access.unregister;
if (!ok && access.write)
newPropertyValue = "unregister";
break;
case CREATE:
ok = checkCreateAccess(access, arg);
if (!ok && access.write)
newPropertyValue = "create " + arg;
break;
default:
throw new AssertionError();
}
if (ok)
return;
}
}
SecurityException se = new SecurityException("Access denied! Invalid " +
"access level for requested MBeanServer operation.");
// Add some more information to help people with deployments that
// worked before we required explicit create clauses. We're not giving
// any information to the bad guys, other than that the access control
// is based on a file, which they could have worked out from the stack
// trace anyway.
if (newPropertyValue != null) {
SecurityException se2 = new SecurityException("Access property " +
"for this identity should be similar to: " + READWRITE +
" " + newPropertyValue);
se.initCause(se2);
}
throw se;
}
private static boolean checkCreateAccess(Access access, String className) {
for (String classNamePattern : access.createPatterns) {
if (classNameMatch(classNamePattern, className))
return true;
}
return false;
}
private static boolean classNameMatch(String pattern, String className) {
// We studiously avoided regexes when parsing the properties file,
// because that is done whenever the VM is started with the
// appropriate -Dcom.sun.management options, even if nobody ever
// creates an MBean. We don't want to incur the overhead of loading
// all the regex code whenever those options are specified, but if we
// get as far as here then the VM is already running and somebody is
// doing the very unusual operation of remotely creating an MBean.
// Because that operation is so unusual, we don't try to optimize
// by hand-matching or by caching compiled Pattern objects.
StringBuilder sb = new StringBuilder();
StringTokenizer stok = new StringTokenizer(pattern, "*", true);
while (stok.hasMoreTokens()) {
String tok = stok.nextToken();
if (tok.equals("*"))
sb.append("[^.]*");
else
sb.append(Pattern.quote(tok));
}
return className.matches(sb.toString());
}
private void parseProperties(Properties props) {
this.accessMap = new HashMap<String, Access>();
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String identity = (String) entry.getKey();
String accessString = (String) entry.getValue();
Access access = Parser.parseAccess(identity, accessString);
accessMap.put(identity, access);
}
}
private static class Parser {
private final static int EOS = -1; // pseudo-codepoint "end of string"
static {
assert !Character.isWhitespace(EOS);
}
private final String identity; // just for better error messages
private final String s; // the string we're parsing
private final int len; // s.length()
private int i;
private int c;
// At any point, either c is s.codePointAt(i), or i == len and
// c is EOS. We use int rather than char because it is conceivable
// (if unlikely) that a classname in a create clause might contain
// "supplementary characters", the ones that don't fit in the original
// 16 bits for Unicode.
private Parser(String identity, String s) {
this.identity = identity;
this.s = s;
this.len = s.length();
this.i = 0;
if (i < len)
this.c = s.codePointAt(i);
else
this.c = EOS;
}
static Access parseAccess(String identity, String s) {
return new Parser(identity, s).parseAccess();
}
private Access parseAccess() {
skipSpace();
String type = parseWord();
Access access;
if (type.equals(READONLY))
access = new Access(false, false, null);
else if (type.equals(READWRITE))
access = parseReadWrite();
else {
throw syntax("Expected " + READONLY + " or " + READWRITE +
": " + type);
}
if (c != EOS)
throw syntax("Extra text at end of line");
return access;
}
private Access parseReadWrite() {
List<String> createClasses = new ArrayList<String>();
boolean unregister = false;
while (true) {
skipSpace();
if (c == EOS)
break;
String type = parseWord();
if (type.equals(UNREGISTER))
unregister = true;
else if (type.equals(CREATE))
parseCreate(createClasses);
else
throw syntax("Unrecognized keyword " + type);
}
return new Access(true, unregister, createClasses);
}
private void parseCreate(List<String> createClasses) {
while (true) {
skipSpace();
createClasses.add(parseClassName());
skipSpace();
if (c == ',')
next();
else
break;
}
}
private String parseClassName() {
// We don't check that classname components begin with suitable
// characters (so we accept 1.2.3 for example). This means that
// there are only two states, which we can call dotOK and !dotOK
// according as a dot (.) is legal or not. Initially we're in
// !dotOK since a classname can't start with a dot; after a dot
// we're in !dotOK again; and after any other characters we're in
// dotOK. The classname is only accepted if we end in dotOK,
// so we reject an empty name or a name that ends with a dot.
final int start = i;
boolean dotOK = false;
while (true) {
if (c == '.') {
if (!dotOK)
throw syntax("Bad . in class name");
dotOK = false;
} else if (c == '*' || Character.isJavaIdentifierPart(c))
dotOK = true;
else
break;
next();
}
String className = s.substring(start, i);
if (!dotOK)
throw syntax("Bad class name " + className);
return className;
}
// Advance c and i to the next character, unless already at EOS.
private void next() {
if (c != EOS) {
i += Character.charCount(c);
if (i < len)
c = s.codePointAt(i);
else
c = EOS;
}
}
private void skipSpace() {
while (Character.isWhitespace(c))
next();
}
private String parseWord() {
skipSpace();
if (c == EOS)
throw syntax("Expected word at end of line");
final int start = i;
while (c != EOS && !Character.isWhitespace(c))
next();
String word = s.substring(start, i);
skipSpace();
return word;
}
private IllegalArgumentException syntax(String msg) {
return new IllegalArgumentException(
msg + " [" + identity + " " + s + "]");
}
}
private Map<String, Access> accessMap;
private Properties originalProps;
private String accessFileName;
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2005, 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.security;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.security.auth.Subject;
/**
* <p>This interface allows to control remote access to the
* {@code addNotificationListener} and {@code removeNotificationListener}
* methods when the notification listener parameter is of type
* {@code NotificationListener} and also allows to control remote access
* to the notifications being forwarded to the interested remote listeners.</p>
*
* <p>An implementation of this interface can be supplied to a
* {@code JMXConnectorServer} in the environment map through the
* {@code com.sun.jmx.remote.notification.access.controller}
* environment map property.</p>
*
* @since 1.6
*/
public interface NotificationAccessController {
/**
* This method is called when a remote
* {@link javax.management.remote.JMXConnector} invokes the method
* {@link javax.management.MBeanServerConnection#addNotificationListener(ObjectName,NotificationListener,NotificationFilter,Object)}.
*
* @param connectionId the {@code connectionId} of the remote client
* adding the listener.
* @param name the name of the MBean where the listener is to be added.
* @param subject the authenticated subject representing the remote client.
*
* @throws SecurityException if the remote client with the supplied
* authenticated subject does not have the rights to add a listener
* to the supplied MBean.
*/
public void addNotificationListener(String connectionId,
ObjectName name,
Subject subject)
throws SecurityException;
/**
* This method is called when a remote
* {@link javax.management.remote.JMXConnector} invokes the method
* {@link javax.management.MBeanServerConnection#removeNotificationListener(ObjectName,NotificationListener)}
* or the method
* {@link javax.management.MBeanServerConnection#removeNotificationListener(ObjectName,NotificationListener,NotificationFilter,Object)}.
*
* @param connectionId the {@code connectionId} of the remote client
* removing the listener.
* @param name the name of the MBean where the listener is to be removed.
* @param subject the authenticated subject representing the remote client.
*
* @throws SecurityException if the remote client with the supplied
* authenticated subject does not have the rights to remove a listener
* from the supplied MBean.
*/
public void removeNotificationListener(String connectionId,
ObjectName name,
Subject subject)
throws SecurityException;
/**
* This method is called before the
* {@link javax.management.remote.JMXConnectorServer}
* forwards the notification to the interested remote
* listener represented by the authenticated subject.
*
* @param connectionId the {@code connectionId} of the remote client
* receiving the notification.
* @param name the name of the MBean forwarding the notification.
* @param notification the notification to be forwarded to the interested
* remote listener.
* @param subject the authenticated subject representing the remote client.
*
* @throws SecurityException if the remote client with
* the supplied authenticated subject does not have the
* rights to receive the notification.
*/
public void fetchNotification(String connectionId,
ObjectName name,
Notification notification,
Subject subject)
throws SecurityException;
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2003, 2014, 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.security;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.Permission;
import java.security.Principal;
import java.security.PrivilegedAction;
import javax.security.auth.Subject;
import javax.management.remote.SubjectDelegationPermission;
import java.util.*;
public class SubjectDelegator {
/* Return the AccessControlContext appropriate to execute an
operation on behalf of the delegatedSubject. If the
authenticatedAccessControlContext does not have permission to
delegate to that subject, throw SecurityException. */
public AccessControlContext
delegatedContext(AccessControlContext authenticatedACC,
Subject delegatedSubject,
boolean removeCallerContext)
throws SecurityException {
if (System.getSecurityManager() != null && authenticatedACC == null) {
throw new SecurityException("Illegal AccessControlContext: null");
}
// Check if the subject delegation permission allows the
// authenticated subject to assume the identity of each
// principal in the delegated subject
//
Collection<Principal> ps = getSubjectPrincipals(delegatedSubject);
final Collection<Permission> permissions = new ArrayList<>(ps.size());
for(Principal p : ps) {
final String pname = p.getClass().getName() + "." + p.getName();
permissions.add(new SubjectDelegationPermission(pname));
}
PrivilegedAction<Void> action =
new PrivilegedAction<Void>() {
public Void run() {
for (Permission sdp : permissions) {
AccessController.checkPermission(sdp);
}
return null;
}
};
AccessController.doPrivileged(action, authenticatedACC);
return getDelegatedAcc(delegatedSubject, removeCallerContext);
}
private AccessControlContext getDelegatedAcc(Subject delegatedSubject, boolean removeCallerContext) {
if (removeCallerContext) {
return JMXSubjectDomainCombiner.getDomainCombinerContext(delegatedSubject);
} else {
return JMXSubjectDomainCombiner.getContext(delegatedSubject);
}
}
/**
* Check if the connector server creator can assume the identity of each
* principal in the authenticated subject, i.e. check if the connector
* server creator codebase contains a subject delegation permission for
* each principal present in the authenticated subject.
*
* @return {@code true} if the connector server creator can delegate to all
* the authenticated principals in the subject. Otherwise, {@code false}.
*/
public static synchronized boolean
checkRemoveCallerContext(Subject subject) {
try {
for (Principal p : getSubjectPrincipals(subject)) {
final String pname =
p.getClass().getName() + "." + p.getName();
final Permission sdp =
new SubjectDelegationPermission(pname);
AccessController.checkPermission(sdp);
}
} catch (SecurityException e) {
return false;
}
return true;
}
/**
* Retrieves the {@linkplain Subject} principals
* @param subject The subject
* @return If the {@code Subject} is immutable it will return the principals directly.
* If the {@code Subject} is mutable it will create an unmodifiable copy.
*/
private static Collection<Principal> getSubjectPrincipals(Subject subject) {
if (subject.isReadOnly()) {
return subject.getPrincipals();
}
List<Principal> principals = Arrays.asList(subject.getPrincipals().toArray(new Principal[0]));
return Collections.unmodifiableList(principals);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.util;
import javax.management.loading.ClassLoaderRepository;
public class ClassLoaderWithRepository extends ClassLoader {
public ClassLoaderWithRepository(ClassLoaderRepository clr,
ClassLoader cl2) {
if (clr == null) throw new
IllegalArgumentException("Null ClassLoaderRepository object.");
repository = clr;
this.cl2 = cl2;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> cls;
try {
cls = repository.loadClass(name);
} catch (ClassNotFoundException cne) {
if (cl2 != null) {
return cl2.loadClass(name);
} else {
throw cne;
}
}
if(!cls.getName().equals(name)){
if (cl2 != null) {
return cl2.loadClass(name);
} else {
throw new ClassNotFoundException(name);
}
}
return cls;
}
private ClassLoaderRepository repository;
private ClassLoader cl2;
}

View File

@@ -0,0 +1,246 @@
/*
* 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.util;
import java.util.logging.Logger;
public class ClassLogger {
private static final boolean ok;
private final String className;
private final Logger logger;
static {
/* We attempt to work even if we are running in J2SE 1.3, where
there is no java.util.logging. The technique we use here is
not strictly portable, but it does work with Sun's J2SE 1.3
at least. This is just a best effort: the Right Thing is for
people to use at least J2SE 1.4. */
boolean loaded = false;
try {
Class<?> c = java.util.logging.Logger.class;
loaded = true;
} catch (Error e) {
// OK.
// java.util.logger package is not available in this jvm.
}
ok = loaded;
}
public ClassLogger(String subsystem, String className) {
if (ok)
logger = Logger.getLogger(subsystem);
else
logger = null;
this.className = className;
}
public final boolean traceOn() {
return finerOn();
}
public final boolean debugOn() {
return finestOn();
}
public final boolean warningOn() {
return ok && logger.isLoggable(java.util.logging.Level.WARNING);
}
public final boolean infoOn() {
return ok && logger.isLoggable(java.util.logging.Level.INFO);
}
public final boolean configOn() {
return ok && logger.isLoggable(java.util.logging.Level.CONFIG);
}
public final boolean fineOn() {
return ok && logger.isLoggable(java.util.logging.Level.FINE);
}
public final boolean finerOn() {
return ok && logger.isLoggable(java.util.logging.Level.FINER);
}
public final boolean finestOn() {
return ok && logger.isLoggable(java.util.logging.Level.FINEST);
}
public final void debug(String func, String msg) {
finest(func,msg);
}
public final void debug(String func, Throwable t) {
finest(func,t);
}
public final void debug(String func, String msg, Throwable t) {
finest(func,msg,t);
}
public final void trace(String func, String msg) {
finer(func,msg);
}
public final void trace(String func, Throwable t) {
finer(func,t);
}
public final void trace(String func, String msg, Throwable t) {
finer(func,msg,t);
}
public final void error(String func, String msg) {
severe(func,msg);
}
public final void error(String func, Throwable t) {
severe(func,t);
}
public final void error(String func, String msg, Throwable t) {
severe(func,msg,t);
}
public final void finest(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.FINEST, className, func, msg);
}
public final void finest(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINEST, className, func,
t.toString(), t);
}
public final void finest(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINEST, className, func, msg,
t);
}
public final void finer(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.FINER, className, func, msg);
}
public final void finer(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINER, className, func,
t.toString(), t);
}
public final void finer(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINER, className, func, msg,t);
}
public final void fine(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.FINE, className, func, msg);
}
public final void fine(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINE, className, func,
t.toString(), t);
}
public final void fine(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.FINE, className, func, msg,
t);
}
public final void config(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.CONFIG, className, func, msg);
}
public final void config(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.CONFIG, className, func,
t.toString(), t);
}
public final void config(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.CONFIG, className, func, msg,
t);
}
public final void info(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.INFO, className, func, msg);
}
public final void info(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.INFO, className, func,
t.toString(), t);
}
public final void info(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.INFO, className, func, msg,
t);
}
public final void warning(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.WARNING, className, func, msg);
}
public final void warning(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.WARNING, className, func,
t.toString(), t);
}
public final void warning(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.WARNING, className, func, msg,
t);
}
public final void severe(String func, String msg) {
if (ok)
logger.logp(java.util.logging.Level.SEVERE, className, func, msg);
}
public final void severe(String func, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.SEVERE, className, func,
t.toString(), t);
}
public final void severe(String func, String msg, Throwable t) {
if (ok)
logger.logp(java.util.logging.Level.SEVERE, className, func, msg,
t);
}
}

View File

@@ -0,0 +1,783 @@
/*
* Copyright (c) 2003, 2016, 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.util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import java.security.AccessController;
import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.InstanceNotFoundException;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServerFactory;
import com.sun.jmx.mbeanserver.GetPropertyAction;
import com.sun.jmx.remote.security.NotificationAccessController;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServer;
public class EnvHelp {
/**
* Name of the attribute that specifies a list of class names acceptable
* as parameters to the {@link RMIServer#newClient(java.lang.Object) RMIServer.newClient()}
* remote method call.
* <p>
* This list of classes should correspond to the transitive closure of the
* credentials class (or classes) used by the installed {@linkplain JMXAuthenticator}
* associated with the {@linkplain RMIServer} implementation.
* <p>
* If the attribute is not set, or is null, then any class is
* deemed acceptable.
*/
public static final String CREDENTIAL_TYPES =
"jmx.remote.rmi.server.credential.types";
/**
* <p>Name of the attribute that specifies a default class loader
* object.
* The value associated with this attribute is a ClassLoader object</p>
*/
private static final String DEFAULT_CLASS_LOADER =
JMXConnectorFactory.DEFAULT_CLASS_LOADER;
/**
* <p>Name of the attribute that specifies a default class loader
* ObjectName.
* The value associated with this attribute is an ObjectName object</p>
*/
private static final String DEFAULT_CLASS_LOADER_NAME =
JMXConnectorServerFactory.DEFAULT_CLASS_LOADER_NAME;
/**
* Get the Connector Server default class loader.
* <p>
* Returns:
* <p>
* <ul>
* <li>
* The ClassLoader object found in <var>env</var> for
* <code>jmx.remote.default.class.loader</code>, if any.
* </li>
* <li>
* The ClassLoader pointed to by the ObjectName found in
* <var>env</var> for <code>jmx.remote.default.class.loader.name</code>,
* and registered in <var>mbs</var> if any.
* </li>
* <li>
* The current thread's context classloader otherwise.
* </li>
* </ul>
*
* @param env Environment attributes.
* @param mbs The MBeanServer for which the connector server provides
* remote access.
*
* @return the connector server's default class loader.
*
* @exception IllegalArgumentException if one of the following is true:
* <ul>
* <li>both
* <code>jmx.remote.default.class.loader</code> and
* <code>jmx.remote.default.class.loader.name</code> are specified,
* </li>
* <li>or
* <code>jmx.remote.default.class.loader</code> is not
* an instance of {@link ClassLoader},
* </li>
* <li>or
* <code>jmx.remote.default.class.loader.name</code> is not
* an instance of {@link ObjectName},
* </li>
* <li>or
* <code>jmx.remote.default.class.loader.name</code> is specified
* but <var>mbs</var> is null.
* </li>
* @exception InstanceNotFoundException if
* <code>jmx.remote.default.class.loader.name</code> is specified
* and the ClassLoader MBean is not found in <var>mbs</var>.
*/
public static ClassLoader resolveServerClassLoader(Map<String, ?> env,
MBeanServer mbs)
throws InstanceNotFoundException {
if (env == null)
return Thread.currentThread().getContextClassLoader();
Object loader = env.get(DEFAULT_CLASS_LOADER);
Object name = env.get(DEFAULT_CLASS_LOADER_NAME);
if (loader != null && name != null) {
final String msg = "Only one of " +
DEFAULT_CLASS_LOADER + " or " +
DEFAULT_CLASS_LOADER_NAME +
" should be specified.";
throw new IllegalArgumentException(msg);
}
if (loader == null && name == null)
return Thread.currentThread().getContextClassLoader();
if (loader != null) {
if (loader instanceof ClassLoader) {
return (ClassLoader) loader;
} else {
final String msg =
"ClassLoader object is not an instance of " +
ClassLoader.class.getName() + " : " +
loader.getClass().getName();
throw new IllegalArgumentException(msg);
}
}
ObjectName on;
if (name instanceof ObjectName) {
on = (ObjectName) name;
} else {
final String msg =
"ClassLoader name is not an instance of " +
ObjectName.class.getName() + " : " +
name.getClass().getName();
throw new IllegalArgumentException(msg);
}
if (mbs == null)
throw new IllegalArgumentException("Null MBeanServer object");
return mbs.getClassLoader(on);
}
/**
* Get the Connector Client default class loader.
* <p>
* Returns:
* <p>
* <ul>
* <li>
* The ClassLoader object found in <var>env</var> for
* <code>jmx.remote.default.class.loader</code>, if any.
* </li>
* <li>The <tt>Thread.currentThread().getContextClassLoader()</tt>
* otherwise.
* </li>
* </ul>
* <p>
* Usually a Connector Client will call
* <pre>
* ClassLoader dcl = EnvHelp.resolveClientClassLoader(env);
* </pre>
* in its <code>connect(Map env)</code> method.
*
* @return The connector client default class loader.
*
* @exception IllegalArgumentException if
* <code>jmx.remote.default.class.loader</code> is specified
* and is not an instance of {@link ClassLoader}.
*/
public static ClassLoader resolveClientClassLoader(Map<String, ?> env) {
if (env == null)
return Thread.currentThread().getContextClassLoader();
Object loader = env.get(DEFAULT_CLASS_LOADER);
if (loader == null)
return Thread.currentThread().getContextClassLoader();
if (loader instanceof ClassLoader) {
return (ClassLoader) loader;
} else {
final String msg =
"ClassLoader object is not an instance of " +
ClassLoader.class.getName() + " : " +
loader.getClass().getName();
throw new IllegalArgumentException(msg);
}
}
/**
* Initialize the cause field of a {@code Throwable} object.
*
* @param throwable The {@code Throwable} on which the cause is set.
* @param cause The cause to set on the supplied {@code Throwable}.
* @return the {@code Throwable} with the cause field initialized.
*/
public static <T extends Throwable> T initCause(T throwable,
Throwable cause) {
throwable.initCause(cause);
return throwable;
}
/**
* Returns the cause field of a {@code Throwable} object.
* The cause field can be got only if <var>t</var> has an
* {@link Throwable#getCause()} method (JDK Version >= 1.4)
* @param t {@code Throwable} on which the cause must be set.
* @return the cause if getCause() succeeded and the got value is not
* null, otherwise return the <var>t</var>.
*/
public static Throwable getCause(Throwable t) {
Throwable ret = t;
try {
java.lang.reflect.Method getCause =
t.getClass().getMethod("getCause", (Class<?>[]) null);
ret = (Throwable)getCause.invoke(t, (Object[]) null);
} catch (Exception e) {
// OK.
// it must be older than 1.4.
}
return (ret != null) ? ret: t;
}
/**
* <p>Name of the attribute that specifies the size of a notification
* buffer for a connector server. The default value is 1000.
*/
public static final String BUFFER_SIZE_PROPERTY =
"jmx.remote.x.notification.buffer.size";
/**
* Returns the size of a notification buffer for a connector server.
* The default value is 1000.
*/
public static int getNotifBufferSize(Map<String, ?> env) {
int defaultQueueSize = 1000; // default value
// keep it for the compability for the fix:
// 6174229: Environment parameter should be notification.buffer.size
// instead of buffer.size
final String oldP = "jmx.remote.x.buffer.size";
// the default value re-specified in the system
try {
GetPropertyAction act = new GetPropertyAction(BUFFER_SIZE_PROPERTY);
String s = AccessController.doPrivileged(act);
if (s != null) {
defaultQueueSize = Integer.parseInt(s);
} else { // try the old one
act = new GetPropertyAction(oldP);
s = AccessController.doPrivileged(act);
if (s != null) {
defaultQueueSize = Integer.parseInt(s);
}
}
} catch (RuntimeException e) {
logger.warning("getNotifBufferSize",
"Can't use System property "+
BUFFER_SIZE_PROPERTY+ ": " + e);
logger.debug("getNotifBufferSize", e);
}
int queueSize = defaultQueueSize;
try {
if (env.containsKey(BUFFER_SIZE_PROPERTY)) {
queueSize = (int)EnvHelp.getIntegerAttribute(env,BUFFER_SIZE_PROPERTY,
defaultQueueSize,0,
Integer.MAX_VALUE);
} else { // try the old one
queueSize = (int)EnvHelp.getIntegerAttribute(env,oldP,
defaultQueueSize,0,
Integer.MAX_VALUE);
}
} catch (RuntimeException e) {
logger.warning("getNotifBufferSize",
"Can't determine queuesize (using default): "+
e);
logger.debug("getNotifBufferSize", e);
}
return queueSize;
}
/**
* <p>Name of the attribute that specifies the maximum number of
* notifications that a client will fetch from its server.. The
* value associated with this attribute should be an
* <code>Integer</code> object. The default value is 1000.</p>
*/
public static final String MAX_FETCH_NOTIFS =
"jmx.remote.x.notification.fetch.max";
/**
* Returns the maximum notification number which a client will
* fetch every time.
*/
public static int getMaxFetchNotifNumber(Map<String, ?> env) {
return (int) getIntegerAttribute(env, MAX_FETCH_NOTIFS, 1000, 1,
Integer.MAX_VALUE);
}
/**
* <p>Name of the attribute that specifies the timeout for a
* client to fetch notifications from its server. The value
* associated with this attribute should be a <code>Long</code>
* object. The default value is 60000 milliseconds.</p>
*/
public static final String FETCH_TIMEOUT =
"jmx.remote.x.notification.fetch.timeout";
/**
* Returns the timeout for a client to fetch notifications.
*/
public static long getFetchTimeout(Map<String, ?> env) {
return getIntegerAttribute(env, FETCH_TIMEOUT, 60000L, 0,
Long.MAX_VALUE);
}
/**
* <p>Name of the attribute that specifies an object that will check
* accesses to add/removeNotificationListener and also attempts to
* receive notifications. The value associated with this attribute
* should be a <code>NotificationAccessController</code> object.
* The default value is null.</p>
* This field is not public because of its com.sun dependency.
*/
public static final String NOTIF_ACCESS_CONTROLLER =
"com.sun.jmx.remote.notification.access.controller";
public static NotificationAccessController getNotificationAccessController(
Map<String, ?> env) {
return (env == null) ? null :
(NotificationAccessController) env.get(NOTIF_ACCESS_CONTROLLER);
}
/**
* Get an integer-valued attribute with name <code>name</code>
* from <code>env</code>. If <code>env</code> is null, or does
* not contain an entry for <code>name</code>, return
* <code>defaultValue</code>. The value may be a Number, or it
* may be a String that is parsable as a long. It must be at
* least <code>minValue</code> and at most<code>maxValue</code>.
*
* @throws IllegalArgumentException if <code>env</code> contains
* an entry for <code>name</code> but it does not meet the
* constraints above.
*/
public static long getIntegerAttribute(Map<String, ?> env, String name,
long defaultValue, long minValue,
long maxValue) {
final Object o;
if (env == null || (o = env.get(name)) == null)
return defaultValue;
final long result;
if (o instanceof Number)
result = ((Number) o).longValue();
else if (o instanceof String) {
result = Long.parseLong((String) o);
/* May throw a NumberFormatException, which is an
IllegalArgumentException. */
} else {
final String msg =
"Attribute " + name + " value must be Integer or String: " + o;
throw new IllegalArgumentException(msg);
}
if (result < minValue) {
final String msg =
"Attribute " + name + " value must be at least " + minValue +
": " + result;
throw new IllegalArgumentException(msg);
}
if (result > maxValue) {
final String msg =
"Attribute " + name + " value must be at most " + maxValue +
": " + result;
throw new IllegalArgumentException(msg);
}
return result;
}
public static final String DEFAULT_ORB="java.naming.corba.orb";
/* Check that all attributes have a key that is a String.
Could make further checks, e.g. appropriate types for attributes. */
public static void checkAttributes(Map<?, ?> attributes) {
for (Object key : attributes.keySet()) {
if (!(key instanceof String)) {
final String msg =
"Attributes contain key that is not a string: " + key;
throw new IllegalArgumentException(msg);
}
}
}
/* Return a writable map containing only those attributes that are
serializable, and that are not hidden by
jmx.remote.x.hidden.attributes or the default list of hidden
attributes. */
public static <V> Map<String, V> filterAttributes(Map<String, V> attributes) {
if (logger.traceOn()) {
logger.trace("filterAttributes", "starts");
}
SortedMap<String, V> map = new TreeMap<String, V>(attributes);
purgeUnserializable(map.values());
hideAttributes(map);
return map;
}
/**
* Remove from the given Collection any element that is not a
* serializable object.
*/
private static void purgeUnserializable(Collection<?> objects) {
logger.trace("purgeUnserializable", "starts");
ObjectOutputStream oos = null;
int i = 0;
for (Iterator<?> it = objects.iterator(); it.hasNext(); i++) {
Object v = it.next();
if (v == null || v instanceof String) {
if (logger.traceOn()) {
logger.trace("purgeUnserializable",
"Value trivially serializable: " + v);
}
continue;
}
try {
if (oos == null)
oos = new ObjectOutputStream(new SinkOutputStream());
oos.writeObject(v);
if (logger.traceOn()) {
logger.trace("purgeUnserializable",
"Value serializable: " + v);
}
} catch (IOException e) {
if (logger.traceOn()) {
logger.trace("purgeUnserializable",
"Value not serializable: " + v + ": " +
e);
}
it.remove();
oos = null; // ObjectOutputStream invalid after exception
}
}
}
/**
* The value of this attribute, if present, is a string specifying
* what other attributes should not appear in
* JMXConnectorServer.getAttributes(). It is a space-separated
* list of attribute patterns, where each pattern is either an
* attribute name, or an attribute prefix followed by a "*"
* character. The "*" has no special significance anywhere except
* at the end of a pattern. By default, this list is added to the
* list defined by {@link #DEFAULT_HIDDEN_ATTRIBUTES} (which
* uses the same format). If the value of this attribute begins
* with an "=", then the remainder of the string defines the
* complete list of attribute patterns.
*/
public static final String HIDDEN_ATTRIBUTES =
"jmx.remote.x.hidden.attributes";
/**
* Default list of attributes not to show.
* @see #HIDDEN_ATTRIBUTES
*/
/* This list is copied directly from the spec, plus
java.naming.security.*. Most of the attributes here would have
been eliminated from the map anyway because they are typically
not serializable. But just in case they are, we list them here
to conform to the spec. */
public static final String DEFAULT_HIDDEN_ATTRIBUTES =
"java.naming.security.* " +
"jmx.remote.authenticator " +
"jmx.remote.context " +
"jmx.remote.default.class.loader " +
"jmx.remote.message.connection.server " +
"jmx.remote.object.wrapping " +
"jmx.remote.rmi.client.socket.factory " +
"jmx.remote.rmi.server.socket.factory " +
"jmx.remote.sasl.callback.handler " +
"jmx.remote.tls.socket.factory " +
"jmx.remote.x.access.file " +
"jmx.remote.x.password.file ";
private static final SortedSet<String> defaultHiddenStrings =
new TreeSet<String>();
private static final SortedSet<String> defaultHiddenPrefixes =
new TreeSet<String>();
private static void hideAttributes(SortedMap<String, ?> map) {
if (map.isEmpty())
return;
final SortedSet<String> hiddenStrings;
final SortedSet<String> hiddenPrefixes;
String hide = (String) map.get(HIDDEN_ATTRIBUTES);
if (hide != null) {
if (hide.startsWith("="))
hide = hide.substring(1);
else
hide += " " + DEFAULT_HIDDEN_ATTRIBUTES;
hiddenStrings = new TreeSet<String>();
hiddenPrefixes = new TreeSet<String>();
parseHiddenAttributes(hide, hiddenStrings, hiddenPrefixes);
} else {
hide = DEFAULT_HIDDEN_ATTRIBUTES;
synchronized (defaultHiddenStrings) {
if (defaultHiddenStrings.isEmpty()) {
parseHiddenAttributes(hide,
defaultHiddenStrings,
defaultHiddenPrefixes);
}
hiddenStrings = defaultHiddenStrings;
hiddenPrefixes = defaultHiddenPrefixes;
}
}
/* Construct a string that is greater than any key in the map.
Setting a string-to-match or a prefix-to-match to this string
guarantees that we will never call next() on the corresponding
iterator. */
String sentinelKey = map.lastKey() + "X";
Iterator<String> keyIterator = map.keySet().iterator();
Iterator<String> stringIterator = hiddenStrings.iterator();
Iterator<String> prefixIterator = hiddenPrefixes.iterator();
String nextString;
if (stringIterator.hasNext())
nextString = stringIterator.next();
else
nextString = sentinelKey;
String nextPrefix;
if (prefixIterator.hasNext())
nextPrefix = prefixIterator.next();
else
nextPrefix = sentinelKey;
/* Read each key in sorted order and, if it matches a string
or prefix, remove it. */
keys:
while (keyIterator.hasNext()) {
String key = keyIterator.next();
/* Continue through string-match values until we find one
that is either greater than the current key, or equal
to it. In the latter case, remove the key. */
int cmp = +1;
while ((cmp = nextString.compareTo(key)) < 0) {
if (stringIterator.hasNext())
nextString = stringIterator.next();
else
nextString = sentinelKey;
}
if (cmp == 0) {
keyIterator.remove();
continue keys;
}
/* Continue through the prefix values until we find one
that is either greater than the current key, or a
prefix of it. In the latter case, remove the key. */
while (nextPrefix.compareTo(key) <= 0) {
if (key.startsWith(nextPrefix)) {
keyIterator.remove();
continue keys;
}
if (prefixIterator.hasNext())
nextPrefix = prefixIterator.next();
else
nextPrefix = sentinelKey;
}
}
}
private static void parseHiddenAttributes(String hide,
SortedSet<String> hiddenStrings,
SortedSet<String> hiddenPrefixes) {
final StringTokenizer tok = new StringTokenizer(hide);
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
if (s.endsWith("*"))
hiddenPrefixes.add(s.substring(0, s.length() - 1));
else
hiddenStrings.add(s);
}
}
/**
* <p>Name of the attribute that specifies the timeout to keep a
* server side connection after answering last client request.
* The default value is 120000 milliseconds.</p>
*/
public static final String SERVER_CONNECTION_TIMEOUT =
"jmx.remote.x.server.connection.timeout";
/**
* Returns the server side connection timeout.
*/
public static long getServerConnectionTimeout(Map<String, ?> env) {
return getIntegerAttribute(env, SERVER_CONNECTION_TIMEOUT, 120000L,
0, Long.MAX_VALUE);
}
/**
* <p>Name of the attribute that specifies the period in
* millisecond for a client to check its connection. The default
* value is 60000 milliseconds.</p>
*/
public static final String CLIENT_CONNECTION_CHECK_PERIOD =
"jmx.remote.x.client.connection.check.period";
/**
* Returns the client connection check period.
*/
public static long getConnectionCheckPeriod(Map<String, ?> env) {
return getIntegerAttribute(env, CLIENT_CONNECTION_CHECK_PERIOD, 60000L,
0, Long.MAX_VALUE);
}
/**
* Computes a boolean value from a string value retrieved from a
* property in the given map.
*
* @param stringBoolean the string value that must be converted
* into a boolean value.
*
* @return
* <ul>
* <li>{@code false} if {@code stringBoolean} is {@code null}</li>
* <li>{@code false} if
* {@code stringBoolean.equalsIgnoreCase("false")}
* is {@code true}</li>
* <li>{@code true} if
* {@code stringBoolean.equalsIgnoreCase("true")}
* is {@code true}</li>
* </ul>
*
* @throws IllegalArgumentException if
* {@code ((String)env.get(prop)).equalsIgnoreCase("false")} and
* {@code ((String)env.get(prop)).equalsIgnoreCase("true")} are
* {@code false}.
*/
public static boolean computeBooleanFromString(String stringBoolean) {
// returns a default value of 'false' if no property is found...
return computeBooleanFromString(stringBoolean,false);
}
/**
* Computes a boolean value from a string value retrieved from a
* property in the given map.
*
* @param stringBoolean the string value that must be converted
* into a boolean value.
* @param defaultValue a default value to return in case no property
* was defined.
*
* @return
* <ul>
* <li>{@code defaultValue} if {@code stringBoolean}
* is {@code null}</li>
* <li>{@code false} if
* {@code stringBoolean.equalsIgnoreCase("false")}
* is {@code true}</li>
* <li>{@code true} if
* {@code stringBoolean.equalsIgnoreCase("true")}
* is {@code true}</li>
* </ul>
*
* @throws IllegalArgumentException if
* {@code ((String)env.get(prop)).equalsIgnoreCase("false")} and
* {@code ((String)env.get(prop)).equalsIgnoreCase("true")} are
* {@code false}.
*/
public static boolean computeBooleanFromString( String stringBoolean, boolean defaultValue) {
if (stringBoolean == null)
return defaultValue;
else if (stringBoolean.equalsIgnoreCase("true"))
return true;
else if (stringBoolean.equalsIgnoreCase("false"))
return false;
else
throw new IllegalArgumentException(
"Property value must be \"true\" or \"false\" instead of \"" +
stringBoolean + "\"");
}
/**
* Converts a map into a valid hash table, i.e.
* it removes all the 'null' values from the map.
*/
public static <K, V> Hashtable<K, V> mapToHashtable(Map<K, V> map) {
HashMap<K, V> m = new HashMap<K, V>(map);
if (m.containsKey(null)) m.remove(null);
for (Iterator<?> i = m.values().iterator(); i.hasNext(); )
if (i.next() == null) i.remove();
return new Hashtable<K, V>(m);
}
/**
* <p>Name of the attribute that specifies whether a connector server
* should not prevent the VM from exiting
*/
public static final String JMX_SERVER_DAEMON = "jmx.remote.x.daemon";
/**
* Returns true if {@value SERVER_DAEMON} is specified in the {@code env}
* as a key and its value is a String and it is equal to true ignoring case.
*
* @param env
* @return
*/
public static boolean isServerDaemon(Map<String, ?> env) {
return (env != null) &&
("true".equalsIgnoreCase((String)env.get(JMX_SERVER_DAEMON)));
}
private static final class SinkOutputStream extends OutputStream {
public void write(byte[] b, int off, int len) {}
public void write(int b) {}
}
private static final ClassLogger logger =
new ClassLogger("javax.management.remote.misc", "EnvHelp");
}

View File

@@ -0,0 +1,51 @@
/*
* 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.util;
import sun.reflect.misc.ReflectUtil;
public class OrderClassLoaders extends ClassLoader {
public OrderClassLoaders(ClassLoader cl1, ClassLoader cl2) {
super(cl1);
this.cl2 = cl2;
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
ReflectUtil.checkPackageAccess(name);
try {
return super.loadClass(name, resolve);
} catch (ClassNotFoundException cne) {
if (cl2 != null) {
return cl2.loadClass(name);
} else {
throw cne;
}
}
}
private ClassLoader cl2;
}